bluesky-mcp-server
Server Details
Search Bluesky posts and feeds; read profiles, threads, and trends. STDIO or Streamable HTTP.
- Status
- Healthy
- Uptime
- 99.9% over 74 days
- Last Tested
- Transport
- Streamable HTTP · MCP 2025-11-25
- URL
- Repository
- cyanheads/bluesky-mcp-server
- GitHub Stars
- 2
- Server Listing
- bluesky-mcp-server
TDQS
Scored across 9 tools
Each tool targets a distinct resource: author feed, feed generator, social graph edges, quotes, threads, profiles, trends, and actor/feed search. The descriptions actively cross-reference siblings (e.g. thread vs quotes, author feed vs feed generator, search_actors vs get_profile), leaving little room for misselection.
All nine tools follow a strict bsky_<verb>_<noun> pattern with a shared namespace prefix and only two verbs (get/search) used consistently. The noun components are unambiguous and predictable.
Nine read-only tools is well-scoped for a public Bluesky browsing server — enough to cover feeds, threads, graphs, and discovery without redundancy. No tool feels padded or vestigial.
The surface is entirely read-only and coherent, but there is a notable gap: no post-text search (bsky_search_feeds explicitly only searches feed metadata), and no way to list likes or reposts on a post despite exposing engagement counts. Agents doing topical discovery or audience analysis hit a dead end.
Available Tools
9 toolsbsky_get_author_feedGet Bluesky Author FeedARead-onlyIdempotentInspect
Get a Bluesky user's recent feed ordered newest-first. Filter by post type: "posts_with_replies" (everything), "posts_no_replies" (excludes replies), "posts_and_author_threads" (posts the author started), "posts_with_media" (the actor's own posts with images or video — no link cards), or "posts_with_video" (the actor's own video posts). The first three include reposts, so items authored by other accounts appear alongside the actor's own writing — a "repostedBy" field marks those, and the "author" field always names who actually wrote the post; the two media filters return no reposts. Set include_pins to also get the post pinned to the profile, marked "pinned", first on the first page — in addition to "limit", and whether or not it matches the filter. Returns posts with full text, engagement counts, embeds, and AT-URIs for drilling into threads via bsky_get_post_thread. Because "limit" counts reposts too, a page from an account that reposts heavily holds far fewer of that account's own posts than the limit suggests; the enrichment fields report the split, so read "originalPosts" rather than the limit when you want the actor's own writing. Supports cursor pagination.
| Name | Required | Description | Default |
|---|---|---|---|
| actor | Yes | Handle (e.g. "alice.bsky.social") or DID of the author whose feed to fetch. A leading "@" and the account's bsky.app page ("https://bsky.app/profile/alice.bsky.social") are accepted and read as the handle or DID they carry. A bare name without a dot is not a handle — use bsky_search_actors to resolve one. | |
| limit | No | Maximum number of posts to return. Default 25; values above 100 are clamped with a notice unless read-limit clamping is disabled. A page that would pass the 48,000-byte response budget comes back with fewer posts and "budgetCapped: true"; its cursor continues from the first post it left out. | |
| cursor | No | Opaque pagination cursor from a previous response for the same actor, passed back unchanged. Omit for the first page. | |
| filter | No | Filter for post types: "posts_no_replies" excludes replies, "posts_with_replies" for everything, "posts_and_author_threads" for threads the author started — all three include reposts, so check "repostedBy" on each item. "posts_with_media" returns the actor's own posts with images or video, not link cards, and "posts_with_video" their video posts; neither includes reposts. | posts_no_replies |
| include_pins | No | Also return the post pinned to the actor's profile, marked "pinned: true", first on the first page. It arrives in addition to "limit", whether or not it matches "filter", and is not repeated on later pages. Default false. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The limit applied to this page. |
| error | No | Present when the call failed. Absent on success. |
| posts | No | Feed items, newest-first — the actor's own posts and the posts they reposted. Items carrying "repostedBy" were written by the account named in "author", not by the requested actor. |
| shown | No | Number of posts returned on this page. |
| cursor | No | Opaque cursor for the next page. Absent on the last page. |
| notice | No | Guidance when the result set is empty or constrained. |
| reposts | No | How many items on this page are posts the requested actor reposted rather than wrote. Present only when there is at least one; these items carry "repostedBy". |
| truncated | No | True when more posts exist beyond this page (a cursor was returned). |
| budgetCapped | No | True when a page of the requested limit would have passed this server's 48,000-byte response budget, so Bluesky was asked again for fewer posts and that page was returned whole. The cursor comes from that same response, so paging on from it skips nothing. Independent of "truncated", which still means only that a cursor was returned. |
| originalPosts | No | How many items on this page the requested actor wrote, a pinned post included. Present whenever the page carries at least one repost — the number a caller asking for the actor's own writing is after, since under the filters that include reposts "limit" counts them too. |
| totalReturned | No | Number of posts in this response page. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the read-only annotations, the description discloses key behavior: newest-first ordering, repost inclusion per filter, repostedBy and author fields, pinned post handling, limit behavior including repost counting, budget-capped responses, and cursor pagination.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but front-loaded and every sentence carries operational meaning. Despite being a single paragraph, it efficiently covers filter semantics, repost caveats, pinned posts, pagination, and thread follow-up.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, 100% schema coverage, annotations, and an output schema, the description is complete. It explains the non-obvious repost and limit interactions and points to the relevant follow-up tool without redundantly re-documenting the schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, but the description adds substantial meaning: it explains each filter's repost behavior, how include_pins interacts with limit and filter, and why limit may not equal the actor's own post count.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: fetch a Bluesky user's recent feed, ordered newest-first. It clearly distinguishes this author-scoped feed from thread exploration by pointing to bsky_get_post_thread for drilling into threads.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives detailed guidance on filter modes and include_pins, and clarifies that bsky_get_post_thread is the next step for threads. It does not explicitly compare this tool against bsky_get_feed for generic feed use, so it stops short of full when/when-not routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bsky_get_feedGet Bluesky FeedARead-onlyIdempotentInspect
Read the posts a Bluesky feed generator serves, in the order the feed ranks them. Accepts the feed's AT-URI (at:///app.bsky.feed.generator/) or its bsky.app page (https://bsky.app/profile//feed/). Feeds come from the "feedUri" of each bsky_get_trending topic — the way to read what a trend is about — from bsky_search_feeds catalog results, from a quoted feed in a post (an embed with recordKind "generator"), or from a shared link; Discover is at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot. Returns posts with full text, engagement counts, embeds, and AT-URIs for drilling into threads via bsky_get_post_thread. A post the feed pinned to its top carries "pinned: true"; a repost carries "repostedBy". Personalized feeds, which Bluesky serves only to a signed-in account, cannot be read here. Supports cursor pagination.
| Name | Required | Description | Default |
|---|---|---|---|
| feed | Yes | The feed to read — its AT-URI, e.g. "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot", or its bsky.app URL, e.g. "https://bsky.app/profile/bsky.app/feed/whats-hot"; a trailing "/", "?…", or "#…" on the URL is ignored. The owner may be a handle or a DID; a handle costs one extra lookup. A trend's "feedUri" works as-is. | |
| limit | No | Maximum number of posts to return. Default 25; values above 100 are clamped with a notice unless read-limit clamping is disabled. A feed may return fewer than the limit on a page that still has more after it — follow the cursor, not the count. A page that would pass the 48,000-byte response budget is asked for again with fewer posts and carries "budgetCapped: true"; its cursor continues after the posts it holds. | |
| cursor | No | Opaque pagination cursor from a previous response of the same feed. Omit for the first page. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The limit applied to this page. |
| error | No | Present when the call failed. Absent on success. |
| posts | No | Posts the feed served, in the order it ranked them. |
| shown | No | Number of posts returned on this page. |
| cursor | No | Opaque cursor for the next page. Absent when the feed has nothing further. |
| notice | No | Guidance when the feed returned nothing, or was cut. |
| truncated | No | True when the feed has more posts after this page (a cursor was returned). |
| budgetCapped | No | True when a page of the requested limit would have passed this server's 48,000-byte response budget, so the feed was asked again for fewer posts and that page was returned whole. The cursor comes from that same response, so paging on from it continues where these posts end. Independent of "truncated", which still means only that a cursor was returned. |
| totalReturned | No | Number of posts in this response page. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/openWorld/idempotent, and the description still adds real behavioral context: pinned posts carry "pinned: true", reposts carry "repostedBy", pages over the 48,000-byte budget are refetched with "budgetCapped: true", limits above 100 are clamped with a notice, and a short page may still have more. None of that is derivable from the annotations or schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and input format lead, with behavioral caveats trailing. The middle sentence listing four feed sources is long and comma-chained, but every clause carries actionable routing information; nothing is padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and full parameter documentation, the description still covers the pagination contract, clamping, budget capping, and the signed-in-only limitation — everything an agent needs to call this correctly on the first attempt.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description earns above that by noting that a handle costs an extra lookup versus a DID, that trailing '/', '?…', '#…' on a URL is ignored, and that callers should follow the cursor rather than the returned count.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource: 'Read the posts a Bluesky feed generator serves, in the order the feed ranks them.' The 'feed generator' framing cleanly separates it from bsky_get_author_feed and bsky_search_feeds, and the ordering behavior is stated up front.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Enumerates concrete sources for the feed argument — a trend's "feedUri" from bsky_get_trending, bsky_search_feeds catalog results, a quoted generator embed, or a shared link — and names an explicit exclusion: personalized feeds 'cannot be read here.' That is when-to-use, where-the-input-comes-from, and when-not all in one place.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bsky_get_followsGet Bluesky Social GraphARead-onlyIdempotentInspect
Fetch the social graph edges for a Bluesky account — who follows them, or who they follow. Returns paginated actor profiles (handle, DID, displayName, bio, pronouns when set, and Bluesky verification status) plus a summary of the subject account. Follower, following, and post counts and the website are not on this view, for the listed accounts or the subject — bsky_get_profile returns them for one account. Accounts with large social graphs return only the first page; use cursor pagination to walk through the full list, or sort "top" to put the accounts Bluesky ranks most prominent first.
| Name | Required | Description | Default |
|---|---|---|---|
| sort | No | Order of the list. "latest" (Bluesky's default, and what omitting this gives) puts the most recent follows first; "top" is Bluesky's own ranking, which surfaces prominent accounts but is not a follower-count order. Pass a cursor back with the same sort it came from. | |
| actor | Yes | Handle (e.g. "alice.bsky.social") or DID of the account to query. A leading "@" and the account's bsky.app page ("https://bsky.app/profile/alice.bsky.social") are accepted and read as the handle or DID they carry. A bare name without a dot is not a handle — use bsky_search_actors to resolve one. | |
| limit | No | Maximum number of actors to return per page. Default 25; values above 100 are clamped with a notice unless read-limit clamping is disabled. | |
| cursor | No | Opaque pagination cursor from a previous response. Omit for the first page. | |
| direction | Yes | "followers" returns accounts that follow this actor. "following" returns accounts this actor follows. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The limit applied to this page. |
| error | No | Present when the call failed. Absent on success. |
| shown | No | Number of actors returned on this page. |
| actors | No | Actors in the requested direction of the social graph. |
| cursor | No | Opaque cursor for the next page. Absent on the last page. |
| notice | No | Guidance when the result set is empty or constrained. |
| subject | No | Profile summary of the queried actor. |
| truncated | No | True when Bluesky returned a cursor, whatever this page held. A cursor is not proof more accounts exist: the next page can come back empty when the remaining accounts are unavailable. |
| totalReturned | No | Number of actors in this response page. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/openWorld/idempotent, so the bar is low, and the description still adds real value: it names the returned profile fields, states which fields are absent, and warns that large graphs return only the first page. It stops short of explaining cursor expiry or error behavior, so not a full 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the action and scope, then returns, then exclusions, then pagination. Mostly earns its space, though the cursor/sort advice duplicates the schema's own parameter descriptions.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 5-parameter read tool with an output schema and full annotation coverage, this is complete: direction semantics, pagination caveat, field coverage, and the sibling to use for missing data are all addressed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, including detailed enum docs for sort and direction and a pattern note on actor, so the schema carries the parameter burden. The description only restates the pagination/sort guidance already present in the schema, adding little new meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening clause names a specific verb and resource (fetch social graph edges) and immediately disambiguates the two directions (followers vs following). It also explicitly distinguishes itself from bsky_get_profile, which handles the counts this tool omits.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description states when to use the cursor (large graphs), when to use sort="top" (prominence ranking), and when to go elsewhere (bsky_get_profile for follower/following/post counts and website). Alternatives and exclusions are both explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bsky_get_post_quotesGet Bluesky Post QuotesARead-onlyIdempotentInspect
Read the quote posts behind a Bluesky post's "quoteCount" — the posts that embed it with commentary of their own. Sort latest (default) is newest first; top is descending like count as ordered by Bluesky. On Bluesky this is where much of the reaction to a post lives; bsky_get_post_thread returns replies only. Accepts the post's AT-URI (at:///app.bsky.feed.post/) from the "uri" field of any returned post, or its bsky.app URL (https://bsky.app/profile//post/); a handle costs one extra lookup. Returns each quote post with full text, author, engagement counts, and AT-URI. Each result's embed names the queried post by AT-URI and CID only — its text is not repeated on every result — and keeps any media the quoting post attached. "quoteCount" is an upper bound on what this returns: Bluesky's counter keeps quotes that have left the index. Supports cursor pagination.
| Name | Required | Description | Default |
|---|---|---|---|
| uri | Yes | The post whose quotes to read — its AT-URI, e.g. "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l6oveex3ii2l", or its bsky.app URL, e.g. "https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l"; a trailing "/", "?…", or "#…" on the URL is ignored. Posts only — a feed, profile, or list address is rejected. | |
| sort | No | Provider ordering: latest is newest first (default); top is descending like count. Reuse cursors with the same post and sort. | latest |
| limit | No | Maximum number of quote posts to return; values above 100 are clamped with notice unless strict limits are configured. Default 25. Pages often hold fewer than the limit and still continue — follow the cursor, not the count. A page that would pass the 48,000-byte response budget comes back with fewer quotes and "budgetCapped: true"; its cursor continues from the first quote it left out. | |
| cursor | No | Opaque pagination cursor from a previous response for the same post and sort, passed back unchanged. Omit for the first page. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The limit applied to this page. |
| uri | No | AT-URI of the post whose quotes these are, in DID form — the form Bluesky was asked in, whatever form the input took. |
| sort | No | Applied provider ordering: latest is newest first; top is descending like count. |
| error | No | Present when the call failed. Absent on success. |
| posts | No | Posts quoting the queried post, in the applied sort order. |
| shown | No | Number of quote posts returned on this page. |
| cursor | No | Opaque cursor for the next page. Absent when there are no more quotes. |
| notice | No | Guidance on the page: that more quotes can be fetched with the returned cursor, or why the page is empty — the post has no readable quotes, or the last page was reached. |
| truncated | No | True when Bluesky returned a cursor, whatever this page held — pages often come back short of the limit with more behind them. |
| budgetCapped | No | True when a page of the requested limit would have passed this server's 48,000-byte response budget, so Bluesky was asked again for fewer quotes and that page was returned whole. The cursor comes from that same response, so paging on from it skips nothing. Independent of "truncated", which still means only that a cursor was returned. |
| totalReturned | No | Number of quote posts in this response page. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint, openWorldHint, and idempotentHint already present, the description adds meaningful behavior beyond annotations: quoteCount is an upper bound because Bluesky retains departed quotes, embeds identify the queried post by AT-URI and CID without repeating its text, and media from quoting posts is preserved.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded and appropriately sized for a tool with format, ordering, pagination, and return-shape nuances. It is slightly longer than strictly necessary because some return details overlap with the output schema, but the added behavioral context mostly earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the annotations, full schema coverage, and an output schema, the description supplies all the context an agent needs: what the tool returns, how quoteCount relates to results, input formats, ordering, and pagination. No material gap remains for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already defines uri, sort, limit, and cursor in detail. The description adds some useful nuance beyond the schema, notably that accepting a handle costs one extra lookup and that cursors are tied to the same post and sort.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and resource: read quote posts behind a Bluesky post's quoteCount. It explicitly distinguishes this from bsky_get_post_thread, which returns replies only, so an agent can select this tool without inspecting the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It names the relevant alternative, bsky_get_post_thread, and explains that quote posts are where reaction content lives whereas replies are separate. This gives clear selection guidance between the two closely related tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bsky_get_post_threadGet Bluesky Post ThreadARead-onlyIdempotentInspect
Fetch the conversation for a post by AT-URI — the parent chain upward and the reply tree downward. Enter the thread at any point and traverse the discussion. AT-URIs have the format "at:////" and are returned in the "uri" field of every post the other post-returning tools emit, such as bsky_get_feed and bsky_get_author_feed; a bsky.app post URL (https://bsky.app/profile//post/) works as-is. Returns the root post, parent chain, and nested replies with per-post author and engagement data. Replies only: quote posts are not part of a thread — read them with bsky_get_post_quotes. The response is often a fraction of the conversation: Bluesky holds replies back past a per-post limit and offers no way to page the rest, so a thread with thousands of replies commonly returns a few hundred. Any node returning fewer replies than its own replyCount carries "truncated: true" with "unreturnedReplies" and a "truncationReason" — "depth" means the reply tree ended there and fetching that node's AT-URI as its own thread continues below it, "unavailable" means no request closes the gap. Read "unreturnedReplies" as an upper bound on what is missing rather than a count of readable replies: Bluesky's counter also includes replies that have left the index, so a small difference often means nothing is left to fetch. The parent chain is disclosed the same way: when it stops at parent_height instead of at the start of the conversation, the topmost node carries "parentChainTruncated: true" and fetching its AT-URI as its own thread continues upward. The enrichment fields total the difference for the whole thread; check them before describing a conversation as complete or naming its first post. A thread that would pass this server's 48,000-byte response budget is cut between whole posts — the target kept first, then its parents nearest-first, then replies level by level — with "budgetCapped: true" and the cut marked where it happened: "budgetOmittedReplyUris" on the target and "budgetOmittedReplies" on a kept reply name the replies left out, "budgetOmittedParents" on the topmost parent the ancestors; fetching those AT-URIs as their own threads reads the rest. In the rendered text nothing is indented: a reply's author heading carries how far it sits below the top-level reply it descends from ("### ↳2"), and every post also names its own parent on a "Reply to" line.
| Name | Required | Description | Default |
|---|---|---|---|
| uri | Yes | AT-URI of the post to fetch, e.g. "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/abc123". All three segments are required — authority (handle or DID), collection, and record key. Obtain from the "uri" field of a post returned by bsky_get_feed or bsky_get_author_feed. The post's bsky.app page, e.g. "https://bsky.app/profile/bsky.app/post/abc123", is accepted and read as the AT-URI it names; a trailing "/", "?…", or "#…" on it is ignored. | |
| depth | No | How many levels of replies to include below the target post. Default 6; values above 10 are clamped with notice unless strict limits are configured — Bluesky itself returns no more than 10 levels however deep the request. Depth does not widen the reply tree either: the per-post reply limit is independent of it. To read below the deepest level returned, fetch an edge node's AT-URI as its own thread. | |
| parent_height | No | How many parent posts to include in the parent chain above the target post. Default 80; values above 100 are clamped with notice unless strict limits are configured. The chain is returned level for level up to this many posts and stops early at the conversation root. When it stops at this bound instead, the topmost node carries "parentChainTruncated: true" — fetch that node's AT-URI as its own thread to read above it. Set to 0 to skip the chain entirely; a reply target then reports the same marker on itself, since its own parent was not returned either. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | Present when the call failed. Absent on success. |
| notice | No | What this response is missing, how much of the gap is explained, and which part of it can still be reached by a further request. |
| thread | No | The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, facets?, author: { did, handle, displayName?, avatar?, verification? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?: [{ val, src?, cts? }], embed?, replyToUri?, replyRootUri? }. facets: [{ index: { byteStart, byteEnd }, features: [{ $type, uri? (link), did? (mention), tag? }] }] — UTF-8 byte ranges with exclusive ends on the unchanged text, including quoted record facets under embed. author.verification: { verifiedStatus, trustedVerifierStatus } — whether a trusted verifier verified the author and whether the author is one, each "valid", "invalid" (verified once, no longer holds), or "none", passed through as Bluesky sends it; absent when Bluesky sent none. quoteCount counts quote posts, which are not part of the thread — read them with bsky_get_post_quotes. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the node's own post.replyCount exceeds the replies returned for it, with unreturnedReplies: the size of that difference, and truncationReason: "depth" (the reply tree ends at this node — fetch its post.uri as its own thread to continue below it) or "unavailable" (no request closes the gap). unreturnedReplies is an upper bound on what is missing, not a count of readable replies: Bluesky's counter keeps including replies that have left the index, so a node reporting one unreturned reply often has none left to fetch. Only reply-tree nodes carry these; a parent-chain node is linear by construction and never reports a reply shortfall. parentChainTruncated?: true on the topmost node above the target when the chain stopped at parent_height rather than at the start of the conversation — that node is a reply to a post this response does not contain, so it is not the conversation root. Fetch that node's post.uri as its own thread to continue upward; parent_height is honored level for level, so the ancestors above it are one request away. Set on the target itself when no parent was returned at all. notFound?: true when the post was deleted or never existed. blocked?: true when its author blocks this view. Both stubs carry the reported AT-URI on post.uri and no content — a blocked node also carries the author DID on post.author.did. Set only when this server's 48,000-byte response budget cut the thread (budgetCapped), on the posts Bluesky did return: budgetOmittedReplyUris?: on the target, the AT-URIs of its direct replies left out, in Bluesky order — fetch each as its own thread (parent_height 0) to read it and everything below it. budgetOmittedReplies?: on any other node, how many of its direct replies were left out, each with everything below it — fetch the node's post.uri as its own thread (parent_height 0). budgetOmittedParents?: on the topmost parent kept, or the target when none was, how many ancestors above it were left out — fetch the node's post.uri with depth 0 to read them. Independent of truncated / unreturnedReplies / parentChainTruncated, which describe what Bluesky itself did not return. |
| truncated | No | True when at least one post in the reply tree returned fewer replies than Bluesky counts for it — counted over every post Bluesky returned, including any the response budget left out. |
| threadgate | No | The thread author's reply restrictions, present only when they set one. Hidden replies are counted in replyCount whether or not they were returned, so a gated thread is one reason the counts run ahead of the tree. |
| budgetCapped | No | True when the whole thread would have passed this server's 48,000-byte response budget, so posts Bluesky returned were left out whole — the target first kept, then its parents nearest-first, then replies level by level. The nodes at the cut carry budgetOmittedReplyUris, budgetOmittedReplies, or budgetOmittedParents, and fetching those AT-URIs reads everything left out. Independent of "truncated" and "parentChainTruncated", which describe what Bluesky did not return. |
| budgetOmitted | No | How many posts Bluesky returned that the response budget left out, set alongside budgetCapped. totalReturned counts the posts kept. |
| totalReturned | No | Thread nodes in this response — the target post, its parent chain, and every reply returned. |
| unreturnedReplies | No | How far the reply counts run ahead of the replies returned, summed across the reply tree. An upper bound on what is missing, not a count of readable replies — Bluesky's counters keep including replies that have left the index. Summed over every post Bluesky returned, including any the response budget left out. Compare against the root post replyCount to judge how much of the conversation is present. |
| parentChainTruncated | No | True when the parent chain stopped at parent_height instead of reaching the start of the conversation, so the topmost post returned above the target is not the conversation root. Independent of "truncated", which covers the reply tree, and unlike it fully recoverable: fetch the topmost parent's AT-URI as its own thread to continue upward. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnly/openWorld/idempotent, but the description discloses the real behavioral hazards: Bluesky withholds replies past a per-post limit with no paging, so threads often return a fraction; nodes carry 'truncated', 'unreturnedReplies', 'truncationReason', 'parentChainTruncated', and 'budgetCapped' markers with different remedies. It also warns that 'unreturnedReplies' is an upper bound, not a readable count — exactly the kind of context annotations cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in sentence one, but the single dense block runs long and re-explains the AT-URI format and depth/parent_height clamping that the schema already states. The truncation and budget mechanics earn their space; the schema duplication does not.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex traversal tool, the description covers identifiers, traversal continuation, truncation semantics, the 48,000-byte budget cut order, and how to read the rendered text indentation. With an output schema present it does not need to re-document return shapes, and it correctly does not.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents defaults, clamping at 10/100 levels, and the parent_height=0 behavior, which the description largely restates. It adds some cross-parameter insight (depth does not widen the tree; the per-post limit is independent), but the AT-URI format explanation duplicates the schema pattern and parameter text nearly verbatim.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence gives a specific verb and resource ('Fetch the conversation for a post by AT-URI') and immediately scopes it as the parent chain upward plus the reply tree downward. It also explicitly distinguishes itself from bsky_get_post_quotes, so an agent can pick the right tool without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains when to use it (enter a thread at any point), where the identifier comes from (the 'uri' field of bsky_get_feed / bsky_get_author_feed, or a bsky.app URL), and names a concrete alternative for the one adjacent case ('quote posts are not part of a thread — read them with bsky_get_post_quotes'). It even prescribes follow-up calls for continuing traversal via truncated AT-URIs.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bsky_get_profileGet Bluesky ProfileARead-onlyIdempotentInspect
Fetch a Bluesky actor's public profile by handle (e.g. "bsky.app") or DID (e.g. "did:plc:z72i7hdynmk6r22z27h6tvur"). Returns displayName, handle, DID, bio, pronouns, website, ordered profile links, follower/following/post counts, avatar URL, moderation labels, pinned post AT-URI, and Bluesky verification — whether the account is verified or a trusted verifier, and who verified it. Use this as the first step to resolve a handle to a DID before calling tools that require a DID or AT-URI. Handles and DIDs are interchangeable as input, and "@bsky.app" or the account's bsky.app page (https://bsky.app/profile/bsky.app) work as-is.
| Name | Required | Description | Default |
|---|---|---|---|
| actor | Yes | Handle (e.g. "bsky.app", "alice.bsky.social") or DID (e.g. "did:plc:z72i7hdynmk6r22z27h6tvur") of the actor to look up. A leading "@" ("@bsky.app") and the account's bsky.app page ("https://bsky.app/profile/bsky.app") are accepted and read as the handle or DID they carry. A bare name without a dot is not a handle — use bsky_search_actors to resolve one. |
Output Schema
| Name | Required | Description |
|---|---|---|
| did | No | Decentralized Identifier — the permanent, portable identity key for this account. |
| error | No | Present when the call failed. Absent on success. |
| links | No | Profile links in upstream display order. Omitted when not supplied; an explicit empty array stays empty. |
| avatar | No | URL of the profile avatar image. |
| handle | No | Human-readable username, e.g. "alice.bsky.social". |
| labels | No | Moderation labels applied to this profile. |
| website | No | URL the account set as its website, in the profile field of that name rather than in the bio. Absent when it set none. Independent of the ordered profile links. |
| pronouns | No | Free-form pronouns the account set, e.g. "they/he". Absent when it set none. Account-authored text bounded only by length, not a fixed vocabulary — read it as written rather than parsing it. |
| createdAt | No | ISO 8601 timestamp of account creation. |
| indexedAt | No | ISO 8601 timestamp when the AppView last indexed this profile. |
| postsCount | No | Total posts authored by this actor. |
| description | No | Biography / about text. |
| displayName | No | Display name set by the user. May differ from the handle. |
| followsCount | No | Number of accounts this actor follows. |
| verification | No | Bluesky verification state — what tells a verified account from a look-alike handle. Absent when Bluesky sent none, which it does for an account neither verified nor a trusted verifier. |
| pinnedPostUri | No | AT-URI of the pinned post, if any. Pass to bsky_get_post_thread to read it. |
| followersCount | No | Number of accounts following this actor. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and openWorldHint, so the safety and repeatability profile is covered. The description adds that input forms are interchangeable ('handles and DIDs are interchangeable', '@' prefix and bsky.app URLs accepted), which is genuine behavioral context, but says nothing about rate limits, auth requirements, or error behavior for unknown actors.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The opening sentence and the resolution guidance are front-loaded and earn their place, but the long enumeration of returned fields (displayName, handle, DID, bio, pronouns, website, links, counts, avatar, labels, pinned post, verification) duplicates what the output schema already provides and pads the definition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists and the parameter schema is fully documented, so the description's job is mainly to establish purpose and call ordering, which it does. It is nearly complete; only failure behavior for unresolvable actors is left unaddressed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single parameter already documents handle/DID/URL/@-prefixed forms with examples and the bare-name caveat. The description restates the same examples rather than adding new semantics, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Fetch a Bluesky actor's public profile') and immediately constrains scope to public profile data. The identifier forms accepted and the list of returned attributes make it unmistakable versus siblings like bsky_search_actors or bsky_get_follows.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells the agent to 'Use this as the first step to resolve a handle to a DID before calling tools that require a DID or AT-URI', which gives a clear ordering rule. It does not name the negative case (e.g., when to prefer bsky_search_actors for a partial name) in the description itself, so it stops short of full alternative routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bsky_get_trendingGet Bluesky Trending TopicsARead-onlyInspect
Fetch the current real-time trending topics on Bluesky. Returns topics with display name, Bluesky's one-sentence summary of the story, post count, category (politics, sports, pop-culture, etc.), status, start time, and the representative accounts driving each topic — so "who is talking about this" needs no follow-up call. Entry point for "what is Bluesky talking about right now". Each trend is a feed: pass its feedUri to bsky_get_feed to read the trend's posts. Note: uses the app.bsky.unspecced.getTrends endpoint, which is not part of Bluesky's stable lexicon and may change without notice.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of trending topics to return. Default 10. Values above Bluesky's maximum of 25 are clamped with notice unless strict limits are configured. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The limit applied to this request. |
| error | No | Present when the call failed. Absent on success. |
| shown | No | Number of trending topics returned. |
| notice | No | Guidance when the result set is empty or constrained. |
| trends | No | Current trending topics, ordered by prominence. |
| truncated | No | True when more topics were trending than limit — raising limit shows them. Never set at limit 25, Bluesky's maximum. |
| totalReturned | No | Number of trending topics returned. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover readOnly and openWorld, but the description adds meaningful context: it enumerates the returned fields (including representative accounts, removing a follow-up need) and warns that the app.bsky.unspecced.getTrends endpoint is not in the stable lexicon and may change without notice. That instability disclosure is real value beyond structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose, then the return shape, then the routing guidance, then the endpoint caveat. Mostly efficient, though the 'who is talking about this needs no follow-up call' phrasing is slightly promotional and could be trimmed.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter read tool with an output schema and safety annotations, the description covers purpose, return contents, downstream routing, and a critical stability caveat. An agent has everything needed to select and call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There is a single parameter with 100% schema coverage, and the schema fully documents the default, max of 25, and clamping behavior. The description adds no parameter detail, but with full schema coverage this hits the baseline of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (fetch) and resource (current real-time trending topics on Bluesky), and positions itself as the entry point for 'what is Bluesky talking about right now'. This clearly distinguishes it from the sibling feed and search tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly frames itself as the starting point for current-topic queries and tells the agent the next step: pass a trend's feedUri to bsky_get_feed to read posts. It lacks an explicit 'do not use this for X' exclusion, but the routing to bsky_get_feed is concrete.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bsky_search_actorsSearch Bluesky ActorsARead-onlyIdempotentInspect
Find Bluesky accounts by name or handle fragment. Returns ranked profiles with handle, DID, displayName, bio, pronouns when the account set them, and Bluesky verification status — which tells a verified account from a look-alike handle. Follower, following, and post counts and the website are not on this view — bsky_get_profile returns them for one account. Use before bsky_get_profile or bsky_get_author_feed when you have a name but not a confirmed handle. Supports cursor-based pagination for browsing beyond the first page of results.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of actors to return. Default 25; values above 100 are clamped with a notice unless read-limit clamping is disabled. | |
| query | Yes | Name or handle fragment to search for, e.g. "alice" or "nytimes.com". Must not be blank. | |
| cursor | No | Opaque pagination cursor from a previous response to the same query. Omit for the first page. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The limit applied to this page. |
| error | No | Present when the call failed. Absent on success. |
| shown | No | Number of actors returned on this page. |
| actors | No | Matching actor profiles, ranked by relevance. |
| cursor | No | Opaque cursor for the next page — pass it back with the same query. Absent on the last page. |
| notice | No | Guidance when the result set is empty or constrained. |
| truncated | No | True when Bluesky returned a cursor for another page, whatever this page held — pages often hold fewer actors than limit and still continue. |
| totalReturned | No | Number of actors in this response page. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/openWorld/idempotent, yet the description adds real value: it lists the returned fields, explains what verification status means (real vs. look-alike handle), and explicitly states what is NOT present (follower/following/post counts, website) plus where to get it. It omits auth or rate-limit context, but is well above the annotation baseline.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose and alternative routing are front-loaded and most sentences earn their place. It is dense but slightly overpacked with return-field enumeration that the output schema already carries.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With full schema coverage, an output schema, and rich annotations, the description supplies exactly the missing pieces: scope, verification semantics, and the boundary with bsky_get_profile. Nothing an agent needs to invoke it correctly is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so query, limit, and cursor are all documented in the schema itself. The description only restates cursor-based pagination already covered by the cursor field, adding no new syntax or format meaning beyond structured data. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource ("Find Bluesky accounts by name or handle fragment") and immediately positions it against bsky_get_profile and bsky_get_author_feed. An agent can distinguish it from the profile/feed siblings without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It names the exact condition for use ("when you have a name but not a confirmed handle") and the two downstream tools it feeds into. The alternatives and prerequisites are explicit rather than inferred.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bsky_search_feedsSearch Bluesky FeedsARead-onlyIdempotentInspect
Search metadata in the provider’s public feed catalog, without credentials. This searches feed names and descriptions, not post text, and does not enumerate every feed. Returns feeds in provider order with generator AT-URIs and creator identities; pass a feedUri to bsky_get_feed to read its posts. Uses app.bsky.unspecced.getPopularFeedGenerators, an unstable upstream API that may change or become unavailable. Continue with the returned opaque cursor and the same query.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum feeds to return. Default 25; values above 100 are clamped with a notice unless read-limit clamping is disabled. | |
| query | Yes | Feed catalog metadata to search, e.g. "science". Must not be blank. | |
| cursor | No | Opaque cursor from a previous response to this same query. Omit for the first page. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cap | No | The limit applied to this page. |
| error | No | Present when the call failed. Absent on success. |
| feeds | No | Matching feeds in provider order. |
| shown | No | Number of feeds returned on this page. |
| cursor | No | Opaque next-page cursor. Its presence signals continuation even when feeds is empty; absent at the end. |
| notice | No | Continuation, empty-result, or clamping guidance. |
| truncated | No | True when a cursor is present, regardless of the number of feeds returned. |
| totalReturned | No | Number of feeds on this page. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only, open-world and idempotent behavior, and the description adds genuinely new traits: no credentials required, results come back in provider order with generator AT-URIs and creator identities, and the upstream method app.bsky.unspecced.getPopularFeedGenerators is unstable and may change or disappear. That last point is exactly the kind of risk an agent can't get from structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four tight sentences, front-loaded with the core action and scope, then exclusions, then return shape and next step, then risk and pagination. No sentence is filler; each adds an operational fact.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a three-parameter, open-world search tool with an output schema, everything an agent needs is present: scope boundaries, auth posture, result ordering, upstream instability, and how to continue paging. Return-value detail is appropriately left to the output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, and the description adds real meaning on top: query targets catalog metadata rather than post text, and cursor is opaque and must be replayed with the identical query. It does not restate the limit clamping rule, but the schema already documents that, so the marginal gain is solid rather than exceptional.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Search metadata in the provider's public feed catalog") and immediately delimits scope by saying it searches feed names and descriptions, not post text. It also names the downstream sibling (bsky_get_feed) for reading posts, so an agent can place it precisely among the feed-related siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly sets expectations about exhaustiveness ("does not enumerate every feed") and routes the agent to bsky_get_feed with a feedUri once a feed is found. Pagination guidance ("Continue with the returned opaque cursor and the same query") gives the agent the exact loop to follow.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
9 tool updates
- Changed
bsky_get_author_feed4 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum number of posts to return (1–100). Default 25. A page that would pass the 48,000-byte response budget comes back with fewer posts and \"budgetCapped: true\"; its cursor continues from the first post it left out."New value: +"Maximum number of posts to return. Default 25; values above 100 are clamped with a notice unless read-limit clamping is disabled. A page that would pass the 48,000-byte response budget comes back with fewer posts and \"budgetCapped: true\"; its cursor continues from the first post it left out." - changed
Input schema / properties / limit / maximumPrevious value: -100New value: +9007199254740991 - changed
Output schema / properties / posts / items / properties / embed / descriptionPrevious value: -"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description }. record: { uri, cid, text?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — embeds is the quoted post's own attachments, so a quote of an image post carries those images here; media is the image/video/link attached alongside the quote by the post doing the quoting, on a recordWithMedia embed. Both are embeds of these same shapes. Bluesky fills embeds for the post being quoted and no deeper, so a quote nested inside another quote ordinarily carries none; omittedEmbeds counts any it did carry that were past the nesting this server follows, so an unattached quote and one whose attachments are missing are never the same value. Fetch the quote uri as its own post to read them. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). A \"generator\" quote is a feed: pass its uri to bsky_get_feed to read it. When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation? }. unknown: { raw } — raw is the upstream $type this server has no mapping for."New value: +"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description }. record: { uri, cid, text?, facets?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — facets retain UTF-8 byte ranges and link URI, mention DID, or tag features on the original text; embeds is the quoted post's own attachments, so a quote of an image post carries those images here; media is the image/video/link attached alongside the quote by the post doing the quoting, on a recordWithMedia embed. Both are embeds of these same shapes. Bluesky fills embeds for the post being quoted and no deeper, so a quote nested inside another quote ordinarily carries none; omittedEmbeds counts any it did carry that were past the nesting this server follows, so an unattached quote and one whose attachments are missing are never the same value. Fetch the quote uri as its own post to read them. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). A \"generator\" quote is a feed: pass its uri to bsky_get_feed to read it. When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation? }. unknown: { raw } — raw is the upstream $type this server has no mapping for." - added
Output schema / properties / posts / items / properties / facetsAdded value: +{ + "description": "Rich-text facets on the unchanged text, with UTF-8 byte ranges and full link, mention, or tag values.", + "items": { + "additionalProperties": {}, + "description": "One rich-text annotation on the original post text.", + "properties": { + "features": { + "description": "Features applied to this range, in upstream order.", + "items": { + "anyOf": [ + { + "additionalProperties": {}, + "description": "Link destination.", + "properties": { + "$type": { + "const": "app.bsky.richtext.facet#link", + "description": "Link feature.", + "type": "string" + }, + "uri": { + "description": "Complete destination URI, independent of abbreviated display text.", + "type": "string" + } + }, + "required": [ + "$type", + "uri" + ], + "type": "object" + }, + { + "additionalProperties": {}, + "description": "Mention identity.", + "properties": { + "$type": { + "const": "app.bsky.richtext.facet#mention", + "description": "Mention feature.", + "type": "string" + }, + "did": { + "description": "Mentioned account DID.", + "type": "string" + } + }, + "required": [ + "$type", + "did" + ], + "type": "object" + }, + { + "additionalProperties": {}, + "description": "Tag value.", + "properties": { + "$type": { + "const": "app.bsky.richtext.facet#tag", + "description": "Tag feature.", + "type": "string" + }, + "tag": { + "description": "Tag text without the leading hash.", + "type": "string" + } + }, + "required": [ + "$type", + "tag" + ], + "type": "object" + }, + { + "additionalProperties": {}, + "description": "An open-union feature whose complete fields are retained as literal metadata.", + "properties": { + "$type": { + "description": "An upstream feature type other than the known link, mention, or tag types.", + "type": "string" + } + }, + "required": [ + "$type" + ], + "type": "object" + } + ], + "description": "A known link, mention, or tag feature, or an upstream extension variant." + }, + "type": "array" + }, + "index": { + "additionalProperties": {}, + "description": "UTF-8 byte range; these are not JavaScript string indices. Additional upstream fields are retained.", + "properties": { + "byteEnd": { + "description": "Exclusive UTF-8 byte offset in the original text.", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "byteStart": { + "description": "Inclusive UTF-8 byte offset in the original text.", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "byteStart", + "byteEnd" + ], + "type": "object" + } + }, + "required": [ + "index", + "features" + ], + "type": "object" + }, + "type": "array" +}
- Changed
bsky_get_feed4 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum number of posts to return (1–100). Default 25. A feed may return fewer than the limit on a page that still has more after it — follow the cursor, not the count. A page that would pass the 48,000-byte response budget is asked for again with fewer posts and carries \"budgetCapped: true\"; its cursor continues after the posts it holds."New value: +"Maximum number of posts to return. Default 25; values above 100 are clamped with a notice unless read-limit clamping is disabled. A feed may return fewer than the limit on a page that still has more after it — follow the cursor, not the count. A page that would pass the 48,000-byte response budget is asked for again with fewer posts and carries \"budgetCapped: true\"; its cursor continues after the posts it holds." - changed
Input schema / properties / limit / maximumPrevious value: -100New value: +9007199254740991 - changed
Output schema / properties / posts / items / properties / embed / descriptionPrevious value: -"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description }. record: { uri, cid, text?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — embeds is the quoted post's own attachments; media is the image/video/link attached alongside the quote by the post doing the quoting, on a recordWithMedia embed. Both are embeds of these same shapes. omittedEmbeds counts attachments past the nesting this server follows — fetch the quote uri as its own post to read them. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). A \"generator\" quote is a feed: pass its uri to bsky_get_feed to read it. When recordKind is set, text and authorHandle are absent because that variant does not carry them. video: { playlist?, thumbnail?, presentation? }. unknown: { raw } — raw is the upstream $type this server has no mapping for."New value: +"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description }. record: { uri, cid, text?, facets?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — facets retain UTF-8 byte ranges and link URI, mention DID, or tag features on the original text; embeds is the quoted post's own attachments; media is the image/video/link attached alongside the quote by the post doing the quoting, on a recordWithMedia embed. Both are embeds of these same shapes. omittedEmbeds counts attachments past the nesting this server follows — fetch the quote uri as its own post to read them. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). A \"generator\" quote is a feed: pass its uri to bsky_get_feed to read it. When recordKind is set, text and authorHandle are absent because that variant does not carry them. video: { playlist?, thumbnail?, presentation? }. unknown: { raw } — raw is the upstream $type this server has no mapping for." - added
Output schema / properties / posts / items / properties / facetsAdded value: +{ + "description": "Rich-text facets on the unchanged text, with UTF-8 byte ranges and full link, mention, or tag values.", + "items": { + "additionalProperties": {}, + "description": "One rich-text annotation on the original post text.", + "properties": { + "features": { + "description": "Features applied to this range, in upstream order.", + "items": { + "anyOf": [ + { + "additionalProperties": {}, + "description": "Link destination.", + "properties": { + "$type": { + "const": "app.bsky.richtext.facet#link", + "description": "Link feature.", + "type": "string" + }, + "uri": { + "description": "Complete destination URI, independent of abbreviated display text.", + "type": "string" + } + }, + "required": [ + "$type", + "uri" + ], + "type": "object" + }, + { + "additionalProperties": {}, + "description": "Mention identity.", + "properties": { + "$type": { + "const": "app.bsky.richtext.facet#mention", + "description": "Mention feature.", + "type": "string" + }, + "did": { + "description": "Mentioned account DID.", + "type": "string" + } + }, + "required": [ + "$type", + "did" + ], + "type": "object" + }, + { + "additionalProperties": {}, + "description": "Tag value.", + "properties": { + "$type": { + "const": "app.bsky.richtext.facet#tag", + "description": "Tag feature.", + "type": "string" + }, + "tag": { + "description": "Tag text without the leading hash.", + "type": "string" + } + }, + "required": [ + "$type", + "tag" + ], + "type": "object" + }, + { + "additionalProperties": {}, + "description": "An open-union feature whose complete fields are retained as literal metadata.", + "properties": { + "$type": { + "description": "An upstream feature type other than the known link, mention, or tag types.", + "type": "string" + } + }, + "required": [ + "$type" + ], + "type": "object" + } + ], + "description": "A known link, mention, or tag feature, or an upstream extension variant." + }, + "type": "array" + }, + "index": { + "additionalProperties": {}, + "description": "UTF-8 byte range; these are not JavaScript string indices. Additional upstream fields are retained.", + "properties": { + "byteEnd": { + "description": "Exclusive UTF-8 byte offset in the original text.", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "byteStart": { + "description": "Inclusive UTF-8 byte offset in the original text.", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "byteStart", + "byteEnd" + ], + "type": "object" + } + }, + "required": [ + "index", + "features" + ], + "type": "object" + }, + "type": "array" +}
- Changed
bsky_get_follows2 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum number of actors to return per page (1–100). Default 25."New value: +"Maximum number of actors to return per page. Default 25; values above 100 are clamped with a notice unless read-limit clamping is disabled." - changed
Input schema / properties / limit / maximumPrevious value: -100New value: +9007199254740991
- Changed
bsky_get_post_quotes9 fields changed- changed
Input schema / properties / cursor / descriptionPrevious value: -"Opaque pagination cursor from a previous response for the same post, passed back unchanged. Omit for the first page."New value: +"Opaque pagination cursor from a previous response for the same post and sort, passed back unchanged. Omit for the first page." - changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum number of quote posts to return (1–100). Default 25. Pages often hold fewer than the limit and still continue — follow the cursor, not the count. A page that would pass the 48,000-byte response budget comes back with fewer quotes and \"budgetCapped: true\"; its cursor continues from the first quote it left out."New value: +"Maximum number of quote posts to return; values above 100 are clamped with notice unless strict limits are configured. Default 25. Pages often hold fewer than the limit and still continue — follow the cursor, not the count. A page that would pass the 48,000-byte response budget comes back with fewer quotes and \"budgetCapped: true\"; its cursor continues from the first quote it left out." - changed
Input schema / properties / limit / maximumPrevious value: -100New value: +9007199254740991 - added
Input schema / properties / sortAdded value: +{ + "default": "latest", + "description": "Provider ordering: latest is newest first (default); top is descending like count. Reuse cursors with the same post and sort.", + "enum": [ + "latest", + "top" + ], + "type": "string" +} - changed
Output schema / anyOfPrevious value: -[ - { - "not": { - "required": [ - "error" - ] - }, - "required": [ - "uri", - "posts", - "totalReturned" - ] - }, - { - "required": [ - "error" - ] - } -]New value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "uri", + "sort", + "posts", + "totalReturned" + ] + }, + { + "required": [ + "error" + ] + } +] - changed
Output schema / properties / posts / descriptionPrevious value: -"Posts quoting the queried post, newest first."New value: +"Posts quoting the queried post, in the applied sort order." - changed
Output schema / properties / posts / items / properties / embed / descriptionPrevious value: -"The embed on this quote post. On every result it is a \"record\" embed pointing at the queried post, cut back to what the quoting post itself carries: { uri, cid, recordKind?, media? } — uri and cid address the queried post (the revision this quote points at), recordKind is set only when that post is unreadable (\"notFound\" | \"blocked\" | \"detached\"), and media is the image/video/link the quoting post attached alongside the quote. The queried post's own text, author, and attachments are not restated on each result. Other shapes, should an embed point elsewhere: images: { images: [{ url, alt }] }; external: { uri, title, description }; record: { uri, cid, text?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? }; video: { playlist?, thumbnail?, presentation? }; unknown: { raw }."New value: +"The embed on this quote post. On every result it is a \"record\" embed pointing at the queried post, cut back to what the quoting post itself carries: { uri, cid, recordKind?, media? } — uri and cid address the queried post (the revision this quote points at), recordKind is set only when that post is unreadable (\"notFound\" | \"blocked\" | \"detached\"), and media is the image/video/link the quoting post attached alongside the quote. The queried post's own text, author, and attachments are not restated on each result. Other shapes, should an embed point elsewhere: images: { images: [{ url, alt }] }; external: { uri, title, description }; record: { uri, cid, text?, facets?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — facets retain UTF-8 byte ranges and full link, mention, or tag features; video: { playlist?, thumbnail?, presentation? }; unknown: { raw }." - added
Output schema / properties / posts / items / properties / facetsAdded value: +{ + "description": "Rich-text facets on the unchanged commentary, with UTF-8 byte ranges and full link, mention, or tag values.", + "items": { + "additionalProperties": {}, + "description": "One rich-text annotation on the original post text.", + "properties": { + "features": { + "description": "Features applied to this range, in upstream order.", + "items": { + "anyOf": [ + { + "additionalProperties": {}, + "description": "Link destination.", + "properties": { + "$type": { + "const": "app.bsky.richtext.facet#link", + "description": "Link feature.", + "type": "string" + }, + "uri": { + "description": "Complete destination URI, independent of abbreviated display text.", + "type": "string" + } + }, + "required": [ + "$type", + "uri" + ], + "type": "object" + }, + { + "additionalProperties": {}, + "description": "Mention identity.", + "properties": { + "$type": { + "const": "app.bsky.richtext.facet#mention", + "description": "Mention feature.", + "type": "string" + }, + "did": { + "description": "Mentioned account DID.", + "type": "string" + } + }, + "required": [ + "$type", + "did" + ], + "type": "object" + }, + { + "additionalProperties": {}, + "description": "Tag value.", + "properties": { + "$type": { + "const": "app.bsky.richtext.facet#tag", + "description": "Tag feature.", + "type": "string" + }, + "tag": { + "description": "Tag text without the leading hash.", + "type": "string" + } + }, + "required": [ + "$type", + "tag" + ], + "type": "object" + }, + { + "additionalProperties": {}, + "description": "An open-union feature whose complete fields are retained as literal metadata.", + "properties": { + "$type": { + "description": "An upstream feature type other than the known link, mention, or tag types.", + "type": "string" + } + }, + "required": [ + "$type" + ], + "type": "object" + } + ], + "description": "A known link, mention, or tag feature, or an upstream extension variant." + }, + "type": "array" + }, + "index": { + "additionalProperties": {}, + "description": "UTF-8 byte range; these are not JavaScript string indices. Additional upstream fields are retained.", + "properties": { + "byteEnd": { + "description": "Exclusive UTF-8 byte offset in the original text.", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "byteStart": { + "description": "Inclusive UTF-8 byte offset in the original text.", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "byteStart", + "byteEnd" + ], + "type": "object" + } + }, + "required": [ + "index", + "features" + ], + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / sortAdded value: +{ + "description": "Applied provider ordering: latest is newest first; top is descending like count.", + "enum": [ + "latest", + "top" + ], + "type": "string" +}
- Changed
bsky_get_post_thread5 fields changed- changed
Input schema / properties / depth / descriptionPrevious value: -"How many levels of replies to include below the target post. Default 6, maximum 10 — Bluesky itself returns no more than 10 levels however deep the request. Depth does not widen the reply tree either: the per-post reply limit is independent of it. To read below the deepest level returned, fetch an edge node's AT-URI as its own thread."New value: +"How many levels of replies to include below the target post. Default 6; values above 10 are clamped with notice unless strict limits are configured — Bluesky itself returns no more than 10 levels however deep the request. Depth does not widen the reply tree either: the per-post reply limit is independent of it. To read below the deepest level returned, fetch an edge node's AT-URI as its own thread." - changed
Input schema / properties / depth / maximumPrevious value: -10New value: +9007199254740991 - changed
Input schema / properties / parent_height / descriptionPrevious value: -"How many parent posts to include in the parent chain above the target post. Default 80, maximum 100. The chain is returned level for level up to this many posts and stops early at the conversation root. When it stops at this bound instead, the topmost node carries \"parentChainTruncated: true\" — fetch that node's AT-URI as its own thread to read above it. Set to 0 to skip the chain entirely; a reply target then reports the same marker on itself, since its own parent was not returned either."New value: +"How many parent posts to include in the parent chain above the target post. Default 80; values above 100 are clamped with notice unless strict limits are configured. The chain is returned level for level up to this many posts and stops early at the conversation root. When it stops at this bound instead, the topmost node carries \"parentChainTruncated: true\" — fetch that node's AT-URI as its own thread to read above it. Set to 0 to skip the chain entirely; a reply target then reports the same marker on itself, since its own parent was not returned either." - changed
Input schema / properties / parent_height / maximumPrevious value: -100New value: +9007199254740991 - changed
Output schema / properties / thread / descriptionPrevious value: -"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, author: { did, handle, displayName?, avatar?, verification? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?: [{ val, src?, cts? }], embed?, replyToUri?, replyRootUri? }. author.verification: { verifiedStatus, trustedVerifierStatus } — whether a trusted verifier verified the author and whether the author is one, each \"valid\", \"invalid\" (verified once, no longer holds), or \"none\", passed through as Bluesky sends it; absent when Bluesky sent none. quoteCount counts quote posts, which are not part of the thread — read them with bsky_get_post_quotes. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the node's own post.replyCount exceeds the replies returned for it, with unreturnedReplies: the size of that difference, and truncationReason: \"depth\" (the reply tree ends at this node — fetch its post.uri as its own thread to continue below it) or \"unavailable\" (no request closes the gap). unreturnedReplies is an upper bound on what is missing, not a count of readable replies: Bluesky's counter keeps including replies that have left the index, so a node reporting one unreturned reply often has none left to fetch. Only reply-tree nodes carry these; a parent-chain node is linear by construction and never reports a reply shortfall. parentChainTruncated?: true on the topmost node above the target when the chain stopped at parent_height rather than at the start of the conversation — that node is a reply to a post this response does not contain, so it is not the conversation root. Fetch that node's post.uri as its own thread to continue upward; parent_height is honored level for level, so the ancestors above it are one request away. Set on the target itself when no parent was returned at all. notFound?: true when the post was deleted or never existed. blocked?: true when its author blocks this view. Both stubs carry the reported AT-URI on post.uri and no content — a blocked node also carries the author DID on post.author.did. Set only when this server's 48,000-byte response budget cut the thread (budgetCapped), on the posts Bluesky did return: budgetOmittedReplyUris?: on the target, the AT-URIs of its direct replies left out, in Bluesky order — fetch each as its own thread (parent_height 0) to read it and everything below it. budgetOmittedReplies?: on any other node, how many of its direct replies were left out, each with everything below it — fetch the node's post.uri as its own thread (parent_height 0). budgetOmittedParents?: on the topmost parent kept, or the target when none was, how many ancestors above it were left out — fetch the node's post.uri with depth 0 to read them. Independent of truncated / unreturnedReplies / parentChainTruncated, which describe what Bluesky itself did not return."New value: +"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, facets?, author: { did, handle, displayName?, avatar?, verification? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?: [{ val, src?, cts? }], embed?, replyToUri?, replyRootUri? }. facets: [{ index: { byteStart, byteEnd }, features: [{ $type, uri? (link), did? (mention), tag? }] }] — UTF-8 byte ranges with exclusive ends on the unchanged text, including quoted record facets under embed. author.verification: { verifiedStatus, trustedVerifierStatus } — whether a trusted verifier verified the author and whether the author is one, each \"valid\", \"invalid\" (verified once, no longer holds), or \"none\", passed through as Bluesky sends it; absent when Bluesky sent none. quoteCount counts quote posts, which are not part of the thread — read them with bsky_get_post_quotes. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the node's own post.replyCount exceeds the replies returned for it, with unreturnedReplies: the size of that difference, and truncationReason: \"depth\" (the reply tree ends at this node — fetch its post.uri as its own thread to continue below it) or \"unavailable\" (no request closes the gap). unreturnedReplies is an upper bound on what is missing, not a count of readable replies: Bluesky's counter keeps including replies that have left the index, so a node reporting one unreturned reply often has none left to fetch. Only reply-tree nodes carry these; a parent-chain node is linear by construction and never reports a reply shortfall. parentChainTruncated?: true on the topmost node above the target when the chain stopped at parent_height rather than at the start of the conversation — that node is a reply to a post this response does not contain, so it is not the conversation root. Fetch that node's post.uri as its own thread to continue upward; parent_height is honored level for level, so the ancestors above it are one request away. Set on the target itself when no parent was returned at all. notFound?: true when the post was deleted or never existed. blocked?: true when its author blocks this view. Both stubs carry the reported AT-URI on post.uri and no content — a blocked node also carries the author DID on post.author.did. Set only when this server's 48,000-byte response budget cut the thread (budgetCapped), on the posts Bluesky did return: budgetOmittedReplyUris?: on the target, the AT-URIs of its direct replies left out, in Bluesky order — fetch each as its own thread (parent_height 0) to read it and everything below it. budgetOmittedReplies?: on any other node, how many of its direct replies were left out, each with everything below it — fetch the node's post.uri as its own thread (parent_height 0). budgetOmittedParents?: on the topmost parent kept, or the target when none was, how many ancestors above it were left out — fetch the node's post.uri with depth 0 to read them. Independent of truncated / unreturnedReplies / parentChainTruncated, which describe what Bluesky itself did not return."
- Changed
bsky_get_profile2 fields changed- added
Output schema / properties / linksAdded value: +{ + "description": "Profile links in upstream display order. Omitted when not supplied; an explicit empty array stays empty.", + "items": { + "additionalProperties": false, + "description": "One outbound profile link.", + "properties": { + "cid": { + "description": "CID of the profile link record.", + "type": "string" + }, + "icon": { + "description": "Icon URL when supplied, including an empty value.", + "type": "string" + }, + "labels": { + "description": "Moderation labels on this link, when supplied.", + "items": { + "additionalProperties": false, + "description": "A moderation label applied by the AppView or a labeler service.", + "properties": { + "cts": { + "description": "ISO 8601 timestamp when the label was applied.", + "type": "string" + }, + "src": { + "description": "DID of the labeler that applied this label.", + "type": "string" + }, + "val": { + "description": "Label value (content warning or moderation tag, e.g. \"porn\", \"spam\").", + "type": "string" + } + }, + "required": [ + "val" + ], + "type": "object" + }, + "type": "array" + }, + "title": { + "description": "Account-authored link title, when supplied, including an empty title.", + "type": "string" + }, + "uri": { + "description": "AT-URI of the profile link record.", + "type": "string" + }, + "url": { + "description": "Complete destination URL.", + "type": "string" + } + }, + "required": [ + "uri", + "cid", + "url" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / properties / website / descriptionPrevious value: -"URL the account set as its website, in the profile field of that name rather than in the bio. Absent when it set none. The one link on a profile that points somewhere else — follow it before reading the bio for one."New value: +"URL the account set as its website, in the profile field of that name rather than in the bio. Absent when it set none. Independent of the ordered profile links."
- Changed
bsky_get_trending2 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum number of trending topics to return (1–25). Default 10. 25 is Bluesky's maximum."New value: +"Maximum number of trending topics to return. Default 10. Values above Bluesky's maximum of 25 are clamped with notice unless strict limits are configured." - changed
Input schema / properties / limit / maximumPrevious value: -25New value: +9007199254740991
- Changed
bsky_search_actors2 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum number of actors to return (1–100). Default 25."New value: +"Maximum number of actors to return. Default 25; values above 100 are clamped with a notice unless read-limit clamping is disabled." - changed
Input schema / properties / limit / maximumPrevious value: -100New value: +9007199254740991
- Added
bsky_search_feeds
7 tool updates
- Changed
bsky_get_author_feed3 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum number of posts to return (1–100). Default 25."New value: +"Maximum number of posts to return (1–100). Default 25. A page that would pass the 48,000-byte response budget comes back with fewer posts and \"budgetCapped: true\"; its cursor continues from the first post it left out." - added
Output schema / properties / budgetCappedAdded value: +{ + "description": "True when a page of the requested limit would have passed this server's 48,000-byte response budget, so Bluesky was asked again for fewer posts and that page was returned whole. The cursor comes from that same response, so paging on from it skips nothing. Independent of \"truncated\", which still means only that a cursor was returned.", + "type": "boolean" +} - added
Output schema / properties / posts / items / properties / author / properties / verificationAdded value: +{ + "additionalProperties": false, + "description": "Bluesky verification of the author — what tells a verified account from a look-alike handle. Absent when Bluesky sent none. Who issued it is on bsky_get_profile.", + "properties": { + "trustedVerifierStatus": { + "description": "Whether the author is itself a trusted verifier — same values as verifiedStatus.", + "type": "string" + }, + "verifiedStatus": { + "description": "Whether a trusted verifier verified the author: \"valid\", \"invalid\" (verified once, no longer holds), or \"none\". Passed through as Bluesky sends it, so another value may appear.", + "type": "string" + } + }, + "required": [ + "verifiedStatus", + "trustedVerifierStatus" + ], + "type": "object" +}
- Changed
bsky_get_feed4 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum number of posts to return (1–100). Default 25. A feed may return fewer than the limit on a page that still has more after it — follow the cursor, not the count."New value: +"Maximum number of posts to return (1–100). Default 25. A feed may return fewer than the limit on a page that still has more after it — follow the cursor, not the count. A page that would pass the 48,000-byte response budget is asked for again with fewer posts and carries \"budgetCapped: true\"; its cursor continues after the posts it holds." - added
Output schema / properties / budgetCappedAdded value: +{ + "description": "True when a page of the requested limit would have passed this server's 48,000-byte response budget, so the feed was asked again for fewer posts and that page was returned whole. The cursor comes from that same response, so paging on from it continues where these posts end. Independent of \"truncated\", which still means only that a cursor was returned.", + "type": "boolean" +} - changed
Output schema / properties / notice / descriptionPrevious value: -"Guidance when the feed returned nothing."New value: +"Guidance when the feed returned nothing, or was cut." - added
Output schema / properties / posts / items / properties / author / properties / verificationAdded value: +{ + "additionalProperties": false, + "description": "Bluesky verification of the author — what tells a verified account from a look-alike handle. Absent when Bluesky sent none. Who issued it is on bsky_get_profile.", + "properties": { + "trustedVerifierStatus": { + "description": "Whether the author is itself a trusted verifier — same values as verifiedStatus.", + "type": "string" + }, + "verifiedStatus": { + "description": "Whether a trusted verifier verified the author: \"valid\", \"invalid\" (verified once, no longer holds), or \"none\". Passed through as Bluesky sends it, so another value may appear.", + "type": "string" + } + }, + "required": [ + "verifiedStatus", + "trustedVerifierStatus" + ], + "type": "object" +}
- Changed
bsky_get_follows2 fields changed- added
Output schema / properties / actors / items / properties / verificationAdded value: +{ + "additionalProperties": false, + "description": "Bluesky verification of this account — what tells it from a look-alike handle. Absent when Bluesky sent none. Who issued it is on bsky_get_profile.", + "properties": { + "trustedVerifierStatus": { + "description": "Whether this account is itself a trusted verifier — same values as verifiedStatus.", + "type": "string" + }, + "verifiedStatus": { + "description": "Whether a trusted verifier verified this account: \"valid\", \"invalid\" (verified once, no longer holds), or \"none\". Passed through as Bluesky sends it, so another value may appear.", + "type": "string" + } + }, + "required": [ + "verifiedStatus", + "trustedVerifierStatus" + ], + "type": "object" +} - added
Output schema / properties / subject / properties / verificationAdded value: +{ + "additionalProperties": false, + "description": "Bluesky verification of this account — what tells it from a look-alike handle. Absent when Bluesky sent none. Who issued it is on bsky_get_profile.", + "properties": { + "trustedVerifierStatus": { + "description": "Whether this account is itself a trusted verifier — same values as verifiedStatus.", + "type": "string" + }, + "verifiedStatus": { + "description": "Whether a trusted verifier verified this account: \"valid\", \"invalid\" (verified once, no longer holds), or \"none\". Passed through as Bluesky sends it, so another value may appear.", + "type": "string" + } + }, + "required": [ + "verifiedStatus", + "trustedVerifierStatus" + ], + "type": "object" +}
- Changed
bsky_get_post_quotes3 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum number of quote posts to return (1–100). Default 25. Pages often hold fewer than the limit and still continue — follow the cursor, not the count."New value: +"Maximum number of quote posts to return (1–100). Default 25. Pages often hold fewer than the limit and still continue — follow the cursor, not the count. A page that would pass the 48,000-byte response budget comes back with fewer quotes and \"budgetCapped: true\"; its cursor continues from the first quote it left out." - added
Output schema / properties / budgetCappedAdded value: +{ + "description": "True when a page of the requested limit would have passed this server's 48,000-byte response budget, so Bluesky was asked again for fewer quotes and that page was returned whole. The cursor comes from that same response, so paging on from it skips nothing. Independent of \"truncated\", which still means only that a cursor was returned.", + "type": "boolean" +} - added
Output schema / properties / posts / items / properties / author / properties / verificationAdded value: +{ + "additionalProperties": false, + "description": "Bluesky verification of the author — what tells a verified account from a look-alike handle. Absent when Bluesky sent none. Who issued it is on bsky_get_profile.", + "properties": { + "trustedVerifierStatus": { + "description": "Whether the author is itself a trusted verifier — same values as verifiedStatus.", + "type": "string" + }, + "verifiedStatus": { + "description": "Whether a trusted verifier verified the author: \"valid\", \"invalid\" (verified once, no longer holds), or \"none\". Passed through as Bluesky sends it, so another value may appear.", + "type": "string" + } + }, + "required": [ + "verifiedStatus", + "trustedVerifierStatus" + ], + "type": "object" +}
- Changed
bsky_get_post_thread5 fields changed- added
Output schema / properties / budgetCappedAdded value: +{ + "description": "True when the whole thread would have passed this server's 48,000-byte response budget, so posts Bluesky returned were left out whole — the target first kept, then its parents nearest-first, then replies level by level. The nodes at the cut carry budgetOmittedReplyUris, budgetOmittedReplies, or budgetOmittedParents, and fetching those AT-URIs reads everything left out. Independent of \"truncated\" and \"parentChainTruncated\", which describe what Bluesky did not return.", + "type": "boolean" +} - added
Output schema / properties / budgetOmittedAdded value: +{ + "description": "How many posts Bluesky returned that the response budget left out, set alongside budgetCapped. totalReturned counts the posts kept.", + "type": "number" +} - changed
Output schema / properties / thread / descriptionPrevious value: -"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, author: { did, handle, displayName?, avatar? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?: [{ val, src?, cts? }], embed?, replyToUri?, replyRootUri? }. quoteCount counts quote posts, which are not part of the thread — read them with bsky_get_post_quotes. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the node's own post.replyCount exceeds the replies returned for it, with unreturnedReplies: the size of that difference, and truncationReason: \"depth\" (the reply tree ends at this node — fetch its post.uri as its own thread to continue below it) or \"unavailable\" (no request closes the gap). unreturnedReplies is an upper bound on what is missing, not a count of readable replies: Bluesky's counter keeps including replies that have left the index, so a node reporting one unreturned reply often has none left to fetch. Only reply-tree nodes carry these; a parent-chain node is linear by construction and never reports a reply shortfall. parentChainTruncated?: true on the topmost node above the target when the chain stopped at parent_height rather than at the start of the conversation — that node is a reply to a post this response does not contain, so it is not the conversation root. Fetch that node's post.uri as its own thread to continue upward; parent_height is honored level for level, so the ancestors above it are one request away. Set on the target itself when no parent was returned at all. notFound?: true when the post was deleted or never existed. blocked?: true when its author blocks this view. Both stubs carry the reported AT-URI on post.uri and no content — a blocked node also carries the author DID on post.author.did."New value: +"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, author: { did, handle, displayName?, avatar?, verification? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?: [{ val, src?, cts? }], embed?, replyToUri?, replyRootUri? }. author.verification: { verifiedStatus, trustedVerifierStatus } — whether a trusted verifier verified the author and whether the author is one, each \"valid\", \"invalid\" (verified once, no longer holds), or \"none\", passed through as Bluesky sends it; absent when Bluesky sent none. quoteCount counts quote posts, which are not part of the thread — read them with bsky_get_post_quotes. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the node's own post.replyCount exceeds the replies returned for it, with unreturnedReplies: the size of that difference, and truncationReason: \"depth\" (the reply tree ends at this node — fetch its post.uri as its own thread to continue below it) or \"unavailable\" (no request closes the gap). unreturnedReplies is an upper bound on what is missing, not a count of readable replies: Bluesky's counter keeps including replies that have left the index, so a node reporting one unreturned reply often has none left to fetch. Only reply-tree nodes carry these; a parent-chain node is linear by construction and never reports a reply shortfall. parentChainTruncated?: true on the topmost node above the target when the chain stopped at parent_height rather than at the start of the conversation — that node is a reply to a post this response does not contain, so it is not the conversation root. Fetch that node's post.uri as its own thread to continue upward; parent_height is honored level for level, so the ancestors above it are one request away. Set on the target itself when no parent was returned at all. notFound?: true when the post was deleted or never existed. blocked?: true when its author blocks this view. Both stubs carry the reported AT-URI on post.uri and no content — a blocked node also carries the author DID on post.author.did. Set only when this server's 48,000-byte response budget cut the thread (budgetCapped), on the posts Bluesky did return: budgetOmittedReplyUris?: on the target, the AT-URIs of its direct replies left out, in Bluesky order — fetch each as its own thread (parent_height 0) to read it and everything below it. budgetOmittedReplies?: on any other node, how many of its direct replies were left out, each with everything below it — fetch the node's post.uri as its own thread (parent_height 0). budgetOmittedParents?: on the topmost parent kept, or the target when none was, how many ancestors above it were left out — fetch the node's post.uri with depth 0 to read them. Independent of truncated / unreturnedReplies / parentChainTruncated, which describe what Bluesky itself did not return." - changed
Output schema / properties / truncated / descriptionPrevious value: -"True when at least one post in the reply tree returned fewer replies than Bluesky counts for it."New value: +"True when at least one post in the reply tree returned fewer replies than Bluesky counts for it — counted over every post Bluesky returned, including any the response budget left out." - changed
Output schema / properties / unreturnedReplies / descriptionPrevious value: -"How far the reply counts run ahead of the replies returned, summed across the reply tree. An upper bound on what is missing, not a count of readable replies — Bluesky's counters keep including replies that have left the index. Compare against the root post replyCount to judge how much of the conversation is present."New value: +"How far the reply counts run ahead of the replies returned, summed across the reply tree. An upper bound on what is missing, not a count of readable replies — Bluesky's counters keep including replies that have left the index. Summed over every post Bluesky returned, including any the response budget left out. Compare against the root post replyCount to judge how much of the conversation is present."
- Changed
bsky_get_profile1 field changed- added
Output schema / properties / verificationAdded value: +{ + "additionalProperties": false, + "description": "Bluesky verification state — what tells a verified account from a look-alike handle. Absent when Bluesky sent none, which it does for an account neither verified nor a trusted verifier.", + "properties": { + "trustedVerifierStatus": { + "description": "Whether this account is itself a trusted verifier, whose verifications Bluesky honors — same values as verifiedStatus.", + "type": "string" + }, + "verifications": { + "description": "Verifications issued by trusted verifiers — empty for an account that verifies others but was never verified itself.", + "items": { + "additionalProperties": false, + "description": "One verification a trusted verifier issued for this account.", + "properties": { + "createdAt": { + "description": "ISO 8601 timestamp when it was issued.", + "type": "string" + }, + "isValid": { + "description": "Whether this verification still holds.", + "type": "boolean" + }, + "issuer": { + "description": "DID of the trusted verifier that issued it.", + "type": "string" + }, + "issuerDisplayName": { + "description": "Display name of the issuer, when Bluesky sent it. Account-authored text.", + "type": "string" + }, + "issuerHandle": { + "description": "Handle of the issuer, when Bluesky sent it.", + "type": "string" + }, + "uri": { + "description": "AT-URI of the verification record.", + "type": "string" + } + }, + "required": [ + "issuer", + "uri", + "isValid", + "createdAt" + ], + "type": "object" + }, + "type": "array" + }, + "verifiedStatus": { + "description": "Whether a trusted verifier verified this account: \"valid\", \"invalid\" (verified once, no longer holds), or \"none\". Passed through as Bluesky sends it, so another value may appear.", + "type": "string" + } + }, + "required": [ + "verifiedStatus", + "trustedVerifierStatus", + "verifications" + ], + "type": "object" +}
- Changed
bsky_search_actors1 field changed- added
Output schema / properties / actors / items / properties / verificationAdded value: +{ + "additionalProperties": false, + "description": "Bluesky verification of this account — what tells it from a look-alike handle. Absent when Bluesky sent none. Who issued it is on bsky_get_profile.", + "properties": { + "trustedVerifierStatus": { + "description": "Whether this account is itself a trusted verifier — same values as verifiedStatus.", + "type": "string" + }, + "verifiedStatus": { + "description": "Whether a trusted verifier verified this account: \"valid\", \"invalid\" (verified once, no longer holds), or \"none\". Passed through as Bluesky sends it, so another value may appear.", + "type": "string" + } + }, + "required": [ + "verifiedStatus", + "trustedVerifierStatus" + ], + "type": "object" +}
7 tool updates
- Changed
bsky_get_author_feed12 fields changed- changed
Input schema / properties / actor / descriptionPrevious value: -"Handle (e.g. \"alice.bsky.social\") or DID of the author whose feed to fetch. A bare name without a dot is not a handle — use bsky_search_actors to resolve one."New value: +"Handle (e.g. \"alice.bsky.social\") or DID of the author whose feed to fetch. A leading \"@\" and the account's bsky.app page (\"https://bsky.app/profile/alice.bsky.social\") are accepted and read as the handle or DID they carry. A bare name without a dot is not a handle — use bsky_search_actors to resolve one." - changed
Input schema / properties / actor / maxLengthPrevious value: -253New value: +2048 - changed
Input schema / properties / actor / patternPrevious value: -"^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])$"New value: +"^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]|@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|https:\\/\\/bsky\\.app\\/profile\\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])\\/?(?:[?#].*)?)$" - changed
Input schema / properties / cursor / descriptionPrevious value: -"Opaque pagination cursor from a previous response. Omit for the first page."New value: +"Opaque pagination cursor from a previous response for the same actor, passed back unchanged. Omit for the first page." - changed
Input schema / properties / filter / descriptionPrevious value: -"Filter for post types: \"posts_no_replies\" excludes replies, \"posts_with_replies\" for everything, \"posts_with_media\" for posts with images/links, \"posts_and_author_threads\" for threads the author started. None of these exclude reposts — the AppView offers no repost filter, so check \"repostedBy\" on each item."New value: +"Filter for post types: \"posts_no_replies\" excludes replies, \"posts_with_replies\" for everything, \"posts_and_author_threads\" for threads the author started — all three include reposts, so check \"repostedBy\" on each item. \"posts_with_media\" returns the actor's own posts with images or video, not link cards, and \"posts_with_video\" their video posts; neither includes reposts." - changed
Input schema / properties / filter / enumPrevious value: -[ - "posts_with_replies", - "posts_no_replies", - "posts_with_media", - "posts_and_author_threads" -]New value: +[ + "posts_with_replies", + "posts_no_replies", + "posts_with_media", + "posts_and_author_threads", + "posts_with_video" +] - added
Input schema / properties / include_pinsAdded value: +{ + "default": false, + "description": "Also return the post pinned to the actor's profile, marked \"pinned: true\", first on the first page. It arrives in addition to \"limit\", whether or not it matches \"filter\", and is not repeated on later pages. Default false.", + "type": "boolean" +} - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `actor_not_found`: The actor handle or DID does not resolve to an existing account. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `actor_not_found`: The actor handle or DID does not resolve to an existing account. `invalid_cursor`: Bluesky could not continue from the cursor the request carried — it answers a cursor it cannot decode with HTTP 500. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / error / properties / data / properties / reason / examplesPrevious value: -[ - "actor_not_found" -]New value: +[ + "actor_not_found", + "invalid_cursor" +] - changed
Output schema / properties / originalPosts / descriptionPrevious value: -"How many items on this page the requested actor wrote. Present whenever the page carries at least one repost — the number a caller asking for the actor's own writing is after, since \"limit\" counts reposts too and no filter excludes them."New value: +"How many items on this page the requested actor wrote, a pinned post included. Present whenever the page carries at least one repost — the number a caller asking for the actor's own writing is after, since under the filters that include reposts \"limit\" counts them too." - added
Output schema / properties / posts / items / properties / pinnedAdded value: +{ + "description": "True on the post the actor pinned to their profile, returned when include_pins is set. A pin is placement, not recency — the post is often older than the items below it. Absent on every other item.", + "type": "boolean" +} - changed
Output schema / properties / posts / items / properties / quoteCount / descriptionPrevious value: -"Number of quote posts."New value: +"Number of quote posts Bluesky counts — read them with bsky_get_post_quotes. An upper bound on what that returns, since the counter keeps quotes that have left the index."
- Changed
bsky_get_feed3 fields changed- changed
Input schema / properties / feed / descriptionPrevious value: -"The feed to read — its AT-URI, e.g. \"at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot\", or its bsky.app URL, e.g. \"https://bsky.app/profile/bsky.app/feed/whats-hot\". The owner may be a handle or a DID; a handle costs one extra lookup. A trend's \"feedUri\" works as-is."New value: +"The feed to read — its AT-URI, e.g. \"at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot\", or its bsky.app URL, e.g. \"https://bsky.app/profile/bsky.app/feed/whats-hot\"; a trailing \"/\", \"?…\", or \"#…\" on the URL is ignored. The owner may be a handle or a DID; a handle costs one extra lookup. A trend's \"feedUri\" works as-is." - changed
Input schema / properties / feed / patternPrevious value: -"^(?:at:\\/\\/((?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])\\/app\\.bsky\\.feed\\.generator\\/([a-zA-Z0-9._~:-]{1,512})|https:\\/\\/bsky\\.app\\/profile\\/((?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])\\/feed\\/([a-zA-Z0-9._~:-]{1,512}))$"New value: +"^(?:at:\\/\\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])\\/app\\.bsky\\.feed\\.generator\\/[a-zA-Z0-9._~:-]{1,512}|https:\\/\\/bsky\\.app\\/profile\\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])\\/feed\\/[a-zA-Z0-9._~:-]{1,512}\\/?(?:[?#].*)?)$" - changed
Output schema / properties / posts / items / properties / quoteCount / descriptionPrevious value: -"Number of quote posts."New value: +"Number of quote posts Bluesky counts — read them with bsky_get_post_quotes. An upper bound on what that returns, since the counter keeps quotes that have left the index."
- Changed
bsky_get_follows4 fields changed- changed
Input schema / properties / actor / descriptionPrevious value: -"Handle (e.g. \"alice.bsky.social\") or DID of the account to query. A bare name without a dot is not a handle — use bsky_search_actors to resolve one."New value: +"Handle (e.g. \"alice.bsky.social\") or DID of the account to query. A leading \"@\" and the account's bsky.app page (\"https://bsky.app/profile/alice.bsky.social\") are accepted and read as the handle or DID they carry. A bare name without a dot is not a handle — use bsky_search_actors to resolve one." - changed
Input schema / properties / actor / maxLengthPrevious value: -253New value: +2048 - changed
Input schema / properties / actor / patternPrevious value: -"^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])$"New value: +"^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]|@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|https:\\/\\/bsky\\.app\\/profile\\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])\\/?(?:[?#].*)?)$" - added
Input schema / properties / sortAdded value: +{ + "description": "Order of the list. \"latest\" (Bluesky's default, and what omitting this gives) puts the most recent follows first; \"top\" is Bluesky's own ranking, which surfaces prominent accounts but is not a follower-count order. Pass a cursor back with the same sort it came from.", + "enum": [ + "latest", + "top" + ], + "type": "string" +}
- Added
bsky_get_post_quotes - Changed
bsky_get_post_thread3 fields changed- changed
Input schema / properties / uri / descriptionPrevious value: -"AT-URI of the post to fetch, e.g. \"at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/abc123\". All three segments are required — authority (handle or DID), collection, and record key. Obtain from the \"uri\" field of a post returned by bsky_get_feed or bsky_get_author_feed."New value: +"AT-URI of the post to fetch, e.g. \"at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/abc123\". All three segments are required — authority (handle or DID), collection, and record key. Obtain from the \"uri\" field of a post returned by bsky_get_feed or bsky_get_author_feed. The post's bsky.app page, e.g. \"https://bsky.app/profile/bsky.app/post/abc123\", is accepted and read as the AT-URI it names; a trailing \"/\", \"?…\", or \"#…\" on it is ignored." - changed
Input schema / properties / uri / patternPrevious value: -"^at:\\/\\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])\\/[a-zA-Z]+(?:\\.[a-zA-Z0-9-]+)+\\/[a-zA-Z0-9._~:-]{1,512}$"New value: +"^(?:at:\\/\\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])\\/[a-zA-Z]+(?:\\.[a-zA-Z0-9-]+)+\\/[a-zA-Z0-9._~:-]{1,512}|https:\\/\\/bsky\\.app\\/profile\\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])\\/post\\/[a-zA-Z0-9._~:-]{1,512}\\/?(?:[?#].*)?)$" - changed
Output schema / properties / thread / descriptionPrevious value: -"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, author: { did, handle, displayName?, avatar? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?: [{ val, src?, cts? }], embed?, replyToUri?, replyRootUri? }. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the node's own post.replyCount exceeds the replies returned for it, with unreturnedReplies: the size of that difference, and truncationReason: \"depth\" (the reply tree ends at this node — fetch its post.uri as its own thread to continue below it) or \"unavailable\" (no request closes the gap). unreturnedReplies is an upper bound on what is missing, not a count of readable replies: Bluesky's counter keeps including replies that have left the index, so a node reporting one unreturned reply often has none left to fetch. Only reply-tree nodes carry these; a parent-chain node is linear by construction and never reports a reply shortfall. parentChainTruncated?: true on the topmost node above the target when the chain stopped at parent_height rather than at the start of the conversation — that node is a reply to a post this response does not contain, so it is not the conversation root. Fetch that node's post.uri as its own thread to continue upward; parent_height is honored level for level, so the ancestors above it are one request away. Set on the target itself when no parent was returned at all. notFound?: true when the post was deleted or never existed. blocked?: true when its author blocks this view. Both stubs carry the reported AT-URI on post.uri and no content — a blocked node also carries the author DID on post.author.did."New value: +"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, author: { did, handle, displayName?, avatar? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?: [{ val, src?, cts? }], embed?, replyToUri?, replyRootUri? }. quoteCount counts quote posts, which are not part of the thread — read them with bsky_get_post_quotes. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the node's own post.replyCount exceeds the replies returned for it, with unreturnedReplies: the size of that difference, and truncationReason: \"depth\" (the reply tree ends at this node — fetch its post.uri as its own thread to continue below it) or \"unavailable\" (no request closes the gap). unreturnedReplies is an upper bound on what is missing, not a count of readable replies: Bluesky's counter keeps including replies that have left the index, so a node reporting one unreturned reply often has none left to fetch. Only reply-tree nodes carry these; a parent-chain node is linear by construction and never reports a reply shortfall. parentChainTruncated?: true on the topmost node above the target when the chain stopped at parent_height rather than at the start of the conversation — that node is a reply to a post this response does not contain, so it is not the conversation root. Fetch that node's post.uri as its own thread to continue upward; parent_height is honored level for level, so the ancestors above it are one request away. Set on the target itself when no parent was returned at all. notFound?: true when the post was deleted or never existed. blocked?: true when its author blocks this view. Both stubs carry the reported AT-URI on post.uri and no content — a blocked node also carries the author DID on post.author.did."
- Changed
bsky_get_profile3 fields changed- changed
Input schema / properties / actor / descriptionPrevious value: -"Handle (e.g. \"bsky.app\", \"alice.bsky.social\") or DID (e.g. \"did:plc:z72i7hdynmk6r22z27h6tvur\") of the actor to look up. A bare name without a dot is not a handle — use bsky_search_actors to resolve one."New value: +"Handle (e.g. \"bsky.app\", \"alice.bsky.social\") or DID (e.g. \"did:plc:z72i7hdynmk6r22z27h6tvur\") of the actor to look up. A leading \"@\" (\"@bsky.app\") and the account's bsky.app page (\"https://bsky.app/profile/bsky.app\") are accepted and read as the handle or DID they carry. A bare name without a dot is not a handle — use bsky_search_actors to resolve one." - changed
Input schema / properties / actor / maxLengthPrevious value: -253New value: +2048 - changed
Input schema / properties / actor / patternPrevious value: -"^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])$"New value: +"^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]|@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|https:\\/\\/bsky\\.app\\/profile\\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])\\/?(?:[?#].*)?)$"
- Changed
bsky_search_actors2 fields changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode."New value: +"Machine-readable failure mode. Declared by this tool: `invalid_cursor`: Bluesky could not continue from the cursor the request carried — it answers a cursor it cannot decode with HTTP 400. Other values are possible when a failure originates below the handler." - added
Output schema / properties / error / properties / data / properties / reason / examplesAdded value: +[ + "invalid_cursor" +]
7 tool updates
- Changed
bsky_get_author_feed1 field changed- changed
Output schema / properties / posts / items / properties / embed / descriptionPrevious value: -"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description }. record: { uri, cid, text?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — embeds is the quoted post's own attachments, so a quote of an image post carries those images here; media is the image/video/link attached alongside the quote by the post doing the quoting, on a recordWithMedia embed. Both are embeds of these same shapes. Bluesky fills embeds for the post being quoted and no deeper, so a quote nested inside another quote ordinarily carries none; omittedEmbeds counts any it did carry that were past the nesting this server follows, so an unattached quote and one whose attachments are missing are never the same value. Fetch the quote uri as its own post to read them. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation? }. unknown: { raw } — raw is the upstream $type this server has no mapping for."New value: +"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description }. record: { uri, cid, text?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — embeds is the quoted post's own attachments, so a quote of an image post carries those images here; media is the image/video/link attached alongside the quote by the post doing the quoting, on a recordWithMedia embed. Both are embeds of these same shapes. Bluesky fills embeds for the post being quoted and no deeper, so a quote nested inside another quote ordinarily carries none; omittedEmbeds counts any it did carry that were past the nesting this server follows, so an unattached quote and one whose attachments are missing are never the same value. Fetch the quote uri as its own post to read them. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). A \"generator\" quote is a feed: pass its uri to bsky_get_feed to read it. When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation? }. unknown: { raw } — raw is the upstream $type this server has no mapping for."
- Added
bsky_get_feed - Changed
bsky_get_follows4 fields changed- removed
Output schema / properties / actors / items / properties / followersCountRemoved value: -{ - "description": "Number of followers.", - "type": "number" -} - removed
Output schema / properties / subject / properties / followersCountRemoved value: -{ - "description": "Subject's follower count.", - "type": "number" -} - removed
Output schema / properties / subject / properties / followsCountRemoved value: -{ - "description": "Subject's following count.", - "type": "number" -} - changed
Output schema / properties / truncated / descriptionPrevious value: -"True when more actors exist beyond this page (a cursor was returned)."New value: +"True when Bluesky returned a cursor, whatever this page held. A cursor is not proof more accounts exist: the next page can come back empty when the remaining accounts are unavailable."
- Changed
bsky_get_post_thread3 fields changed- changed
Input schema / properties / uri / descriptionPrevious value: -"AT-URI of the post to fetch, e.g. \"at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/abc123\". All three segments are required — authority (handle or DID), collection, and record key. Obtain from bsky_search_posts or bsky_get_author_feed."New value: +"AT-URI of the post to fetch, e.g. \"at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/abc123\". All three segments are required — authority (handle or DID), collection, and record key. Obtain from the \"uri\" field of a post returned by bsky_get_feed or bsky_get_author_feed." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `invalid_at_uri`: The AppView rejected the AT-URI — the shape passed the input pattern but the authority, collection, or record key is not one it can resolve. `post_not_found`: The AT-URI is well-formed but the post was deleted or never existed. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `invalid_at_uri`: The AppView rejected the AT-URI — the shape passed the input pattern but the authority, collection, or record key is not one it can resolve. `post_not_found`: The AT-URI is well-formed but the post was deleted or never existed. `uri_is_feed`: The AT-URI names a feed generator (app.bsky.feed.generator), not a post. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / error / properties / data / properties / reason / examplesPrevious value: -[ - "invalid_at_uri", - "post_not_found" -]New value: +[ + "invalid_at_uri", + "post_not_found", + "uri_is_feed" +]
- Changed
bsky_get_trending7 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum number of trending topics to return (1–25). Default 10."New value: +"Maximum number of trending topics to return (1–25). Default 10. 25 is Bluesky's maximum." - added
Output schema / properties / trends / items / properties / descriptionAdded value: +{ + "description": "Bluesky's one-sentence summary of the story behind the trend. Third-party text, rendered quoted.", + "type": "string" +} - added
Output schema / properties / trends / items / properties / feedUriAdded value: +{ + "description": "AT-URI of the feed that collects this trend's posts — pass it to bsky_get_feed as-is to read them. Parsed from link; absent when link is missing or is not a feed page.", + "type": "string" +} - changed
Output schema / properties / trends / items / properties / link / descriptionPrevious value: -"Full URL associated with this trending topic (e.g. https://bsky.app/…), if provided."New value: +"The trend feed's page on bsky.app (https://bsky.app/profile/…/feed/…), if provided." - changed
Output schema / properties / trends / items / properties / status / descriptionPrevious value: -"Velocity signal, e.g. \"hot\" or \"rising\"."New value: +"Velocity signal as Bluesky reports it, e.g. \"hot\", \"cooling\", or \"stale\"." - changed
Output schema / properties / trends / items / properties / topic / descriptionPrevious value: -"Opaque topic slug, e.g. \"ailaunch2025\". Use as a search term in bsky_search_posts."New value: +"Record key of the feed generator behind this trend, e.g. \"1d558a3bc9ff\" — an identifier, not a search term. Read the trend's posts through feedUri." - changed
Output schema / properties / truncated / descriptionPrevious value: -"True when the topic list was capped at the requested limit; more may exist."New value: +"True when more topics were trending than limit — raising limit shows them. Never set at limit 25, Bluesky's maximum."
- Changed
bsky_search_actors4 fields changed- changed
Input schema / properties / cursor / descriptionPrevious value: -"Opaque pagination cursor from a previous response. Note: the public Bluesky AppView restricts cursor-based search pagination for unauthenticated requests — passing a cursor may return a 403 error. Cursor pagination is reliable only for bsky_get_author_feed and bsky_get_follows."New value: +"Opaque pagination cursor from a previous response to the same query. Omit for the first page." - removed
Output schema / properties / actors / items / properties / followersCountRemoved value: -{ - "description": "Number of followers.", - "type": "number" -} - changed
Output schema / properties / cursor / descriptionPrevious value: -"Opaque cursor returned by the API. Unreliable for unauthenticated search requests on the public AppView — passing it on a subsequent call may return a 403 error."New value: +"Opaque cursor for the next page — pass it back with the same query. Absent on the last page." - changed
Output schema / properties / truncated / descriptionPrevious value: -"True when more actors match than were returned on this page."New value: +"True when Bluesky returned a cursor for another page, whatever this page held — pages often hold fewer actors than limit and still continue."
- Removed
bsky_search_posts
1 tool update
- Changed
bsky_get_post_thread1 field changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `invalid_at_uri`: The AppView rejected the AT-URI — the shape passed the input pattern but the authority, collection, or record key is not one it can resolve. `post_not_found`: The AT-URI is valid format but the post was deleted or never existed. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `invalid_at_uri`: The AppView rejected the AT-URI — the shape passed the input pattern but the authority, collection, or record key is not one it can resolve. `post_not_found`: The AT-URI is well-formed but the post was deleted or never existed. Other values are possible when a failure originates below the handler."
7 tool updates
- Changed
bsky_get_author_feed6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "posts", + "totalReturned" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `actor_not_found`: The actor handle or DID does not resolve to an existing account. Other values are possible when a failure originates below the handler.", + "examples": [ + "actor_not_found" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "posts", - "totalReturned" -]
- Changed
bsky_get_follows6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "actors", + "subject", + "totalReturned" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `actor_not_found`: The actor handle or DID does not resolve to an existing account. Other values are possible when a failure originates below the handler.", + "examples": [ + "actor_not_found" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "actors", - "subject", - "totalReturned" -]
- Changed
bsky_get_post_thread6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "thread", + "totalReturned" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `invalid_at_uri`: The AppView rejected the AT-URI — the shape passed the input pattern but the authority, collection, or record key is not one it can resolve. `post_not_found`: The AT-URI is valid format but the post was deleted or never existed. Other values are possible when a failure originates below the handler.", + "examples": [ + "invalid_at_uri", + "post_not_found" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "thread", - "totalReturned" -]
- Changed
bsky_get_profile6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "did", + "handle" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `actor_not_found`: The handle does not resolve or the profile does not exist. Other values are possible when a failure originates below the handler.", + "examples": [ + "actor_not_found" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "did", - "handle" -]
- Changed
bsky_get_trending6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "trends", + "totalReturned" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode.", + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "trends", - "totalReturned" -]
- Changed
bsky_search_actors6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "actors", + "totalReturned" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode.", + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "actors", - "totalReturned" -]
- Changed
bsky_search_posts6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "posts", + "totalReturned" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `upstream_rejected_filter`: Bluesky rejected one of the search parameters and named which one in its response. Other values are possible when a failure originates below the handler.", + "examples": [ + "upstream_rejected_filter" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "posts", - "totalReturned" -]
3 tool updates
- Changed
bsky_get_author_feed4 fields changed- changed
Output schema / properties / posts / items / properties / embed / descriptionPrevious value: -"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt, aspectRatio? } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description, thumb? }. record: { uri, cid, text?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — embeds is the quoted post's own attachments, so a quote of an image post carries those images here; media is the image/video/link attached alongside the quote by the post doing the quoting, on a recordWithMedia embed. Both are embeds of these same shapes. Bluesky fills embeds for the post being quoted and no deeper, so a quote nested inside another quote ordinarily carries none; omittedEmbeds counts any it did carry that were past the nesting this server follows, so an unattached quote and one whose attachments are missing are never the same value. Fetch the quote uri as its own post to read them. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation?, aspectRatio? }. unknown: { raw } — raw is the upstream $type this server has no mapping for."New value: +"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description }. record: { uri, cid, text?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — embeds is the quoted post's own attachments, so a quote of an image post carries those images here; media is the image/video/link attached alongside the quote by the post doing the quoting, on a recordWithMedia embed. Both are embeds of these same shapes. Bluesky fills embeds for the post being quoted and no deeper, so a quote nested inside another quote ordinarily carries none; omittedEmbeds counts any it did carry that were past the nesting this server follows, so an unattached quote and one whose attachments are missing are never the same value. Fetch the quote uri as its own post to read them. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation? }. unknown: { raw } — raw is the upstream $type this server has no mapping for." - changed
Output schema / properties / posts / items / properties / labels / items / descriptionPrevious value: -"A moderation label."New value: +"A moderation label applied by the AppView or a labeler service." - added
Output schema / properties / posts / items / properties / labels / items / properties / ctsAdded value: +{ + "description": "ISO 8601 timestamp when the label was applied.", + "type": "string" +} - added
Output schema / properties / posts / items / properties / labels / items / properties / srcAdded value: +{ + "description": "DID of the labeler that applied this label. Equal to the post author DID when the account labelled its own post, and a labeler service DID otherwise.", + "type": "string" +}
- Changed
bsky_get_post_thread1 field changed- changed
Output schema / properties / thread / descriptionPrevious value: -"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, author: { did, handle, displayName?, avatar? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?, embed?, replyToUri?, replyRootUri? }. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the node's own post.replyCount exceeds the replies returned for it, with unreturnedReplies: the size of that difference, and truncationReason: \"depth\" (the reply tree ends at this node — fetch its post.uri as its own thread to continue below it) or \"unavailable\" (no request closes the gap). unreturnedReplies is an upper bound on what is missing, not a count of readable replies: Bluesky's counter keeps including replies that have left the index, so a node reporting one unreturned reply often has none left to fetch. Only reply-tree nodes carry these; a parent-chain node is linear by construction and never reports a reply shortfall. parentChainTruncated?: true on the topmost node above the target when the chain stopped at parent_height rather than at the start of the conversation — that node is a reply to a post this response does not contain, so it is not the conversation root. Fetch that node's post.uri as its own thread to continue upward; parent_height is honored level for level, so the ancestors above it are one request away. Set on the target itself when no parent was returned at all. notFound?: true when the post was deleted or never existed. blocked?: true when its author blocks this view. Both stubs carry the reported AT-URI on post.uri and no content — a blocked node also carries the author DID on post.author.did."New value: +"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, author: { did, handle, displayName?, avatar? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?: [{ val, src?, cts? }], embed?, replyToUri?, replyRootUri? }. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the node's own post.replyCount exceeds the replies returned for it, with unreturnedReplies: the size of that difference, and truncationReason: \"depth\" (the reply tree ends at this node — fetch its post.uri as its own thread to continue below it) or \"unavailable\" (no request closes the gap). unreturnedReplies is an upper bound on what is missing, not a count of readable replies: Bluesky's counter keeps including replies that have left the index, so a node reporting one unreturned reply often has none left to fetch. Only reply-tree nodes carry these; a parent-chain node is linear by construction and never reports a reply shortfall. parentChainTruncated?: true on the topmost node above the target when the chain stopped at parent_height rather than at the start of the conversation — that node is a reply to a post this response does not contain, so it is not the conversation root. Fetch that node's post.uri as its own thread to continue upward; parent_height is honored level for level, so the ancestors above it are one request away. Set on the target itself when no parent was returned at all. notFound?: true when the post was deleted or never existed. blocked?: true when its author blocks this view. Both stubs carry the reported AT-URI on post.uri and no content — a blocked node also carries the author DID on post.author.did."
- Changed
bsky_search_posts4 fields changed- changed
Output schema / properties / posts / items / properties / embed / descriptionPrevious value: -"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt, aspectRatio? } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description, thumb? }. record: { uri, cid, text?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — embeds is the quoted post's own attachments, so a quote of an image post carries those images here; media is the image/video/link attached alongside the quote by the post doing the quoting, on a recordWithMedia embed. Both are embeds of these same shapes. Bluesky fills embeds for the post being quoted and no deeper, so a quote nested inside another quote ordinarily carries none; omittedEmbeds counts any it did carry that were past the nesting this server follows, so an unattached quote and one whose attachments are missing are never the same value. Fetch the quote uri as its own post to read them. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation?, aspectRatio? }. unknown: { raw } — raw is the upstream $type this server has no mapping for."New value: +"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description }. record: { uri, cid, text?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — embeds is the quoted post's own attachments, so a quote of an image post carries those images here; media is the image/video/link attached alongside the quote by the post doing the quoting, on a recordWithMedia embed. Both are embeds of these same shapes. Bluesky fills embeds for the post being quoted and no deeper, so a quote nested inside another quote ordinarily carries none; omittedEmbeds counts any it did carry that were past the nesting this server follows, so an unattached quote and one whose attachments are missing are never the same value. Fetch the quote uri as its own post to read them. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation? }. unknown: { raw } — raw is the upstream $type this server has no mapping for." - changed
Output schema / properties / posts / items / properties / labels / items / descriptionPrevious value: -"A moderation label."New value: +"A moderation label applied by the AppView or a labeler service." - added
Output schema / properties / posts / items / properties / labels / items / properties / ctsAdded value: +{ + "description": "ISO 8601 timestamp when the label was applied.", + "type": "string" +} - added
Output schema / properties / posts / items / properties / labels / items / properties / srcAdded value: +{ + "description": "DID of the labeler that applied this label. Equal to the post author DID when the account labelled its own post, and a labeler service DID otherwise.", + "type": "string" +}
6 tool updates
- Changed
bsky_get_author_feed3 fields changed- added
Output schema / properties / originalPostsAdded value: +{ + "description": "How many items on this page the requested actor wrote. Present whenever the page carries at least one repost — the number a caller asking for the actor's own writing is after, since \"limit\" counts reposts too and no filter excludes them.", + "type": "number" +} - changed
Output schema / properties / posts / items / properties / embed / descriptionPrevious value: -"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt, aspectRatio? } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description, thumb? }. record: { uri, cid, text?, authorHandle?, media?, recordKind? } — media is the image/video/link attached alongside the quote on a recordWithMedia embed, itself an embed of one of these shapes. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation?, aspectRatio? }. unknown: { raw } — raw is the upstream $type this server has no mapping for."New value: +"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt, aspectRatio? } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description, thumb? }. record: { uri, cid, text?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — embeds is the quoted post's own attachments, so a quote of an image post carries those images here; media is the image/video/link attached alongside the quote by the post doing the quoting, on a recordWithMedia embed. Both are embeds of these same shapes. Bluesky fills embeds for the post being quoted and no deeper, so a quote nested inside another quote ordinarily carries none; omittedEmbeds counts any it did carry that were past the nesting this server follows, so an unattached quote and one whose attachments are missing are never the same value. Fetch the quote uri as its own post to read them. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation?, aspectRatio? }. unknown: { raw } — raw is the upstream $type this server has no mapping for." - added
Output schema / properties / repostsAdded value: +{ + "description": "How many items on this page are posts the requested actor reposted rather than wrote. Present only when there is at least one; these items carry \"repostedBy\".", + "type": "number" +}
- Changed
bsky_get_follows2 fields changed- added
Output schema / properties / actors / items / properties / pronounsAdded value: +{ + "description": "Free-form pronouns the account set, e.g. \"they/he\". Absent when it set none. Account-authored text bounded only by length, not a fixed vocabulary — read it as written rather than parsing it.", + "type": "string" +} - added
Output schema / properties / subject / properties / pronounsAdded value: +{ + "description": "Free-form pronouns the subject account set, e.g. \"they/he\". Absent when it set none.", + "type": "string" +}
- Changed
bsky_get_post_thread3 fields changed- changed
Input schema / properties / parent_height / descriptionPrevious value: -"How many parent posts to include in the parent chain above the target post. Default 80, maximum 100. The chain is returned level for level up to this many posts and stops early at the conversation root. To read above a chain longer than this, fetch the topmost parent returned as its own thread."New value: +"How many parent posts to include in the parent chain above the target post. Default 80, maximum 100. The chain is returned level for level up to this many posts and stops early at the conversation root. When it stops at this bound instead, the topmost node carries \"parentChainTruncated: true\" — fetch that node's AT-URI as its own thread to read above it. Set to 0 to skip the chain entirely; a reply target then reports the same marker on itself, since its own parent was not returned either." - added
Output schema / properties / parentChainTruncatedAdded value: +{ + "description": "True when the parent chain stopped at parent_height instead of reaching the start of the conversation, so the topmost post returned above the target is not the conversation root. Independent of \"truncated\", which covers the reply tree, and unlike it fully recoverable: fetch the topmost parent's AT-URI as its own thread to continue upward.", + "type": "boolean" +} - changed
Output schema / properties / thread / descriptionPrevious value: -"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, author: { did, handle, displayName?, avatar? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?, embed?, replyToUri?, replyRootUri? }. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the node's own post.replyCount exceeds the replies returned for it, with unreturnedReplies: the size of that difference, and truncationReason: \"depth\" (the reply tree ends at this node — fetch its post.uri as its own thread to continue below it) or \"unavailable\" (no request closes the gap). unreturnedReplies is an upper bound on what is missing, not a count of readable replies: Bluesky's counter keeps including replies that have left the index, so a node reporting one unreturned reply often has none left to fetch. Only reply-tree nodes carry these; a parent-chain node is linear by construction and never reports a shortfall. notFound?: true when the post was deleted or never existed. blocked?: true when its author blocks this view. Both stubs carry the reported AT-URI on post.uri and no content — a blocked node also carries the author DID on post.author.did."New value: +"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, author: { did, handle, displayName?, avatar? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?, embed?, replyToUri?, replyRootUri? }. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the node's own post.replyCount exceeds the replies returned for it, with unreturnedReplies: the size of that difference, and truncationReason: \"depth\" (the reply tree ends at this node — fetch its post.uri as its own thread to continue below it) or \"unavailable\" (no request closes the gap). unreturnedReplies is an upper bound on what is missing, not a count of readable replies: Bluesky's counter keeps including replies that have left the index, so a node reporting one unreturned reply often has none left to fetch. Only reply-tree nodes carry these; a parent-chain node is linear by construction and never reports a reply shortfall. parentChainTruncated?: true on the topmost node above the target when the chain stopped at parent_height rather than at the start of the conversation — that node is a reply to a post this response does not contain, so it is not the conversation root. Fetch that node's post.uri as its own thread to continue upward; parent_height is honored level for level, so the ancestors above it are one request away. Set on the target itself when no parent was returned at all. notFound?: true when the post was deleted or never existed. blocked?: true when its author blocks this view. Both stubs carry the reported AT-URI on post.uri and no content — a blocked node also carries the author DID on post.author.did."
- Changed
bsky_get_profile2 fields changed- added
Output schema / properties / pronounsAdded value: +{ + "description": "Free-form pronouns the account set, e.g. \"they/he\". Absent when it set none. Account-authored text bounded only by length, not a fixed vocabulary — read it as written rather than parsing it.", + "type": "string" +} - added
Output schema / properties / websiteAdded value: +{ + "description": "URL the account set as its website, in the profile field of that name rather than in the bio. Absent when it set none. The one link on a profile that points somewhere else — follow it before reading the bio for one.", + "type": "string" +}
- Changed
bsky_search_actors1 field changed- added
Output schema / properties / actors / items / properties / pronounsAdded value: +{ + "description": "Free-form pronouns the account set, e.g. \"they/he\". Absent when it set none. Account-authored text bounded only by length, not a fixed vocabulary — read it as written rather than parsing it.", + "type": "string" +}
- Changed
bsky_search_posts1 field changed- changed
Output schema / properties / posts / items / properties / embed / descriptionPrevious value: -"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt, aspectRatio? } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description, thumb? }. record: { uri, cid, text?, authorHandle?, media?, recordKind? } — media is the image/video/link attached alongside the quote on a recordWithMedia embed, itself an embed of one of these shapes. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation?, aspectRatio? }. unknown: { raw } — raw is the upstream $type this server has no mapping for."New value: +"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt, aspectRatio? } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description, thumb? }. record: { uri, cid, text?, authorHandle?, embeds?, media?, omittedEmbeds?, recordKind? } — embeds is the quoted post's own attachments, so a quote of an image post carries those images here; media is the image/video/link attached alongside the quote by the post doing the quoting, on a recordWithMedia embed. Both are embeds of these same shapes. Bluesky fills embeds for the post being quoted and no deeper, so a quote nested inside another quote ordinarily carries none; omittedEmbeds counts any it did carry that were past the nesting this server follows, so an unattached quote and one whose attachments are missing are never the same value. Fetch the quote uri as its own post to read them. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation?, aspectRatio? }. unknown: { raw } — raw is the upstream $type this server has no mapping for."
1 tool update
- Changed
bsky_search_posts5 fields changed- added
Input schema / properties / language / anyOfAdded value: +[ + { + "const": "", + "type": "string" + }, + { + "description": "BCP-47 language tag.", + "maxLength": 35, + "pattern": "^[a-zA-Z]{2,3}(?:-[a-zA-Z0-9]{1,8})*$", + "type": "string" + } +] - changed
Input schema / properties / language / descriptionPrevious value: -"BCP-47 language code to restrict results to, e.g. \"en\", \"ja\", \"es\"."New value: +"Restrict results to posts tagged with this BCP-47 language tag, e.g. \"en\", \"ja\", \"es\", \"pt-BR\". Pass \"\" or omit for no language filter. Only the shape is checked here, matching Bluesky itself: a well-formed tag that names no indexed language (e.g. \"qqq\") is accepted and the filter is dropped, so results come back unfiltered rather than empty or failing." - removed
Input schema / properties / language / maxLengthRemoved value: -10 - removed
Input schema / properties / language / typeRemoved value: -"string" - changed
Output schema / properties / hitsTotal / descriptionPrevious value: -"Total number of posts matching this query across all pages, when reported by the API. Use to communicate result scale without fetching every page."New value: +"Posts matching this query across all pages, as reported by Bluesky. Capped at 10,000: a value of exactly 10,000 means \"at least 10,000\" and the true total may be far larger, so report it as a lower bound rather than a count. Any smaller value is an exact total. Use to communicate result scale without fetching every page."
1 tool update
- Changed
bsky_get_post_thread11 fields changed- changed
Input schema / properties / depth / descriptionPrevious value: -"How many levels of replies to include in the reply tree. Default 6."New value: +"How many levels of replies to include below the target post. Default 6, maximum 10 — Bluesky itself returns no more than 10 levels however deep the request. Depth does not widen the reply tree either: the per-post reply limit is independent of it. To read below the deepest level returned, fetch an edge node's AT-URI as its own thread." - changed
Input schema / properties / depth / maximumPrevious value: -1000New value: +10 - changed
Input schema / properties / parent_height / descriptionPrevious value: -"How many parent posts to include in the parent chain above the target post. Default 80."New value: +"How many parent posts to include in the parent chain above the target post. Default 80, maximum 100. The chain is returned level for level up to this many posts and stops early at the conversation root. To read above a chain longer than this, fetch the topmost parent returned as its own thread." - changed
Input schema / properties / parent_height / maximumPrevious value: -1000New value: +100 - added
Output schema / properties / noticeAdded value: +{ + "description": "What this response is missing, how much of the gap is explained, and which part of it can still be reached by a further request.", + "type": "string" +} - changed
Output schema / properties / thread / descriptionPrevious value: -"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, author: { did, handle, displayName?, avatar? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?, embed?, replyToUri?, replyRootUri? }. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the API cut off deeper replies. notFound?: true when the post was deleted."New value: +"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, author: { did, handle, displayName?, avatar? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?, embed?, replyToUri?, replyRootUri? }. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the node's own post.replyCount exceeds the replies returned for it, with unreturnedReplies: the size of that difference, and truncationReason: \"depth\" (the reply tree ends at this node — fetch its post.uri as its own thread to continue below it) or \"unavailable\" (no request closes the gap). unreturnedReplies is an upper bound on what is missing, not a count of readable replies: Bluesky's counter keeps including replies that have left the index, so a node reporting one unreturned reply often has none left to fetch. Only reply-tree nodes carry these; a parent-chain node is linear by construction and never reports a shortfall. notFound?: true when the post was deleted or never existed. blocked?: true when its author blocks this view. Both stubs carry the reported AT-URI on post.uri and no content — a blocked node also carries the author DID on post.author.did." - added
Output schema / properties / threadgateAdded value: +{ + "additionalProperties": false, + "description": "The thread author's reply restrictions, present only when they set one. Hidden replies are counted in replyCount whether or not they were returned, so a gated thread is one reason the counts run ahead of the tree.", + "properties": { + "allow": { + "description": "Who may reply. Omitted when anyone may; an empty array means the author turned replies off. Replies posted before the rule was set stay in the thread.", + "items": { + "enum": [ + "follower", + "following", + "list", + "mentioned", + "unknown" + ], + "type": "string" + }, + "type": "array" + }, + "hiddenReplies": { + "description": "AT-URIs of replies the thread author hid. Some are still present in the returned tree — compare against the node URIs rather than assuming every entry is absent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "uri": { + "description": "AT-URI of the threadgate record itself.", + "type": "string" + } + }, + "required": [ + "uri", + "hiddenReplies" + ], + "type": "object" +} - added
Output schema / properties / totalReturnedAdded value: +{ + "description": "Thread nodes in this response — the target post, its parent chain, and every reply returned.", + "type": "number" +} - added
Output schema / properties / truncatedAdded value: +{ + "description": "True when at least one post in the reply tree returned fewer replies than Bluesky counts for it.", + "type": "boolean" +} - added
Output schema / properties / unreturnedRepliesAdded value: +{ + "description": "How far the reply counts run ahead of the replies returned, summed across the reply tree. An upper bound on what is missing, not a count of readable replies — Bluesky's counters keep including replies that have left the index. Compare against the root post replyCount to judge how much of the conversation is present.", + "type": "number" +} - changed
Output schema / requiredPrevious value: -[ - "thread" -]New value: +[ + "thread", + "totalReturned" +]
4 tool updates
- Changed
bsky_get_author_feed7 fields changed- changed
Input schema / properties / filter / descriptionPrevious value: -"Filter for post types: \"posts_no_replies\" for original posts only, \"posts_with_replies\" for everything, \"posts_with_media\" for posts with images/links, \"posts_and_author_threads\" for threads the author started."New value: +"Filter for post types: \"posts_no_replies\" excludes replies, \"posts_with_replies\" for everything, \"posts_with_media\" for posts with images/links, \"posts_and_author_threads\" for threads the author started. None of these exclude reposts — the AppView offers no repost filter, so check \"repostedBy\" on each item." - changed
Output schema / properties / posts / descriptionPrevious value: -"Posts from this author, ordered newest-first."New value: +"Feed items, newest-first — the actor's own posts and the posts they reposted. Items carrying \"repostedBy\" were written by the account named in \"author\", not by the requested actor." - changed
Output schema / properties / posts / items / descriptionPrevious value: -"A single post from the author feed."New value: +"A single item from the author feed — the actor's own post, or a post they reposted." - changed
Output schema / properties / posts / items / properties / embed / descriptionPrevious value: -"Media or link embed attached to this post, if any."New value: +"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt, aspectRatio? } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description, thumb? }. record: { uri, cid, text?, authorHandle?, media?, recordKind? } — media is the image/video/link attached alongside the quote on a recordWithMedia embed, itself an embed of one of these shapes. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation?, aspectRatio? }. unknown: { raw } — raw is the upstream $type this server has no mapping for." - added
Output schema / properties / posts / items / properties / replyRootUriAdded value: +{ + "description": "AT-URI of the post this conversation started from, if this is a reply. Pass to bsky_get_post_thread to read the whole conversation rather than one branch.", + "type": "string" +} - added
Output schema / properties / posts / items / properties / repostedAtAdded value: +{ + "description": "ISO 8601 timestamp of the repost. Present only on reposted items.", + "type": "string" +} - added
Output schema / properties / posts / items / properties / repostedByAdded value: +{ + "additionalProperties": false, + "description": "Present only when this item is a repost rather than the requested actor writing. The post itself — text, author, engagement counts — belongs to the author field, not to this account.", + "properties": { + "did": { + "description": "Permanent DID of the account that reposted.", + "type": "string" + }, + "displayName": { + "description": "Display name of the account that reposted.", + "type": "string" + }, + "handle": { + "description": "Handle of the account that reposted.", + "type": "string" + } + }, + "required": [ + "did", + "handle" + ], + "type": "object" +}
- Changed
bsky_get_post_thread1 field changed- changed
Output schema / properties / thread / descriptionPrevious value: -"The conversation thread rooted at the requested post."New value: +"The conversation thread rooted at the requested post — a recursive node tree. Each node has: post: { uri, cid, text, author: { did, handle, displayName?, avatar? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?, embed?, replyToUri?, replyRootUri? }. parent?: parent thread node. replies?: array of child thread nodes. truncated?: true when the API cut off deeper replies. notFound?: true when the post was deleted."
- Changed
bsky_get_trending1 field changed- added
Output schema / properties / trends / items / properties / actorsAdded value: +{ + "description": "Representative accounts posting about this topic — the AppView returns five per trend. Pass a handle to bsky_get_author_feed or bsky_get_profile instead of searching for authors.", + "items": { + "additionalProperties": false, + "description": "A representative account posting about this topic.", + "properties": { + "did": { + "description": "Permanent DID of the actor.", + "type": "string" + }, + "displayName": { + "description": "Display name set by the actor.", + "type": "string" + }, + "handle": { + "description": "Human-readable handle, e.g. \"alice.bsky.social\".", + "type": "string" + } + }, + "required": [ + "did", + "handle" + ], + "type": "object" + }, + "type": "array" +}
- Changed
bsky_search_posts2 fields changed- changed
Output schema / properties / posts / items / properties / embed / descriptionPrevious value: -"Media or link embed, if any."New value: +"Media or link embed attached to this post. type: \"images\" | \"external\" | \"record\" | \"video\" | \"unknown\". images: array of { url, alt, aspectRatio? } — also carries app.bsky.embed.gallery embeds. external: { uri, title, description, thumb? }. record: { uri, cid, text?, authorHandle?, media?, recordKind? } — media is the image/video/link attached alongside the quote on a recordWithMedia embed, itself an embed of one of these shapes. recordKind is absent for an ordinary quoted post and otherwise names what stood in for one: \"notFound\" | \"blocked\" | \"detached\" (the quote exists but cannot be read) or \"generator\" | \"list\" | \"starterPack\" | \"labeler\" | \"unknown\" (the quoted record is not a post). When recordKind is set, text and authorHandle are absent because that variant does not carry them — do not read the quote as an empty post. video: { playlist?, thumbnail?, presentation?, aspectRatio? }. unknown: { raw } — raw is the upstream $type this server has no mapping for." - added
Output schema / properties / posts / items / properties / replyRootUriAdded value: +{ + "description": "AT-URI of the post this conversation started from, if this is a reply. Pass to bsky_get_post_thread to read the whole conversation rather than one branch.", + "type": "string" +}
6 tool updates
- Changed
bsky_get_author_feed3 fields changed- changed
Input schema / properties / actor / descriptionPrevious value: -"Handle (e.g. \"alice.bsky.social\") or DID of the author whose feed to fetch."New value: +"Handle (e.g. \"alice.bsky.social\") or DID of the author whose feed to fetch. A bare name without a dot is not a handle — use bsky_search_actors to resolve one." - added
Input schema / properties / actor / minLengthAdded value: +1 - added
Input schema / properties / actor / patternAdded value: +"^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])$"
- Changed
bsky_get_follows3 fields changed- changed
Input schema / properties / actor / descriptionPrevious value: -"Handle (e.g. \"alice.bsky.social\") or DID of the account to query."New value: +"Handle (e.g. \"alice.bsky.social\") or DID of the account to query. A bare name without a dot is not a handle — use bsky_search_actors to resolve one." - added
Input schema / properties / actor / minLengthAdded value: +1 - added
Input schema / properties / actor / patternAdded value: +"^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])$"
- Changed
bsky_get_post_thread2 fields changed- changed
Input schema / properties / uri / descriptionPrevious value: -"AT-URI of the post to fetch, e.g. \"at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/abc123\". Obtain from bsky_search_posts or bsky_get_author_feed."New value: +"AT-URI of the post to fetch, e.g. \"at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/abc123\". All three segments are required — authority (handle or DID), collection, and record key. Obtain from bsky_search_posts or bsky_get_author_feed." - added
Input schema / properties / uri / patternAdded value: +"^at:\\/\\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])\\/[a-zA-Z]+(?:\\.[a-zA-Z0-9-]+)+\\/[a-zA-Z0-9._~:-]{1,512}$"
- Changed
bsky_get_profile3 fields changed- changed
Input schema / properties / actor / descriptionPrevious value: -"Handle (e.g. \"bsky.app\", \"alice.bsky.social\") or DID (e.g. \"did:plc:z72i7hdynmk6r22z27h6tvur\") of the actor to look up."New value: +"Handle (e.g. \"bsky.app\", \"alice.bsky.social\") or DID (e.g. \"did:plc:z72i7hdynmk6r22z27h6tvur\") of the actor to look up. A bare name without a dot is not a handle — use bsky_search_actors to resolve one." - added
Input schema / properties / actor / minLengthAdded value: +1 - added
Input schema / properties / actor / patternAdded value: +"^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])$"
- Changed
bsky_search_actors3 fields changed- changed
Input schema / properties / query / descriptionPrevious value: -"Name or handle fragment to search for, e.g. \"alice\" or \"nytimes.com\"."New value: +"Name or handle fragment to search for, e.g. \"alice\" or \"nytimes.com\". Must not be blank." - added
Input schema / properties / query / minLengthAdded value: +1 - added
Input schema / properties / query / patternAdded value: +"\\S"
- Changed
bsky_search_posts15 fields changed- added
Input schema / properties / author_handle / anyOfAdded value: +[ + { + "const": "", + "type": "string" + }, + { + "description": "Handle or DID of the author.", + "maxLength": 253, + "pattern": "^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])$", + "type": "string" + } +] - changed
Input schema / properties / author_handle / descriptionPrevious value: -"Filter to posts by this author. Accepts handle (e.g. \"bsky.app\") or DID. Use bsky_get_profile to resolve a name to a handle first."New value: +"Filter to posts by this author. Accepts handle (e.g. \"bsky.app\") or DID; pass \"\" or omit for no author filter. Use bsky_search_actors to resolve a name to a handle first." - removed
Input schema / properties / author_handle / maxLengthRemoved value: -253 - removed
Input schema / properties / author_handle / typeRemoved value: -"string" - changed
Input schema / properties / query / descriptionPrevious value: -"Full-text search query, e.g. \"climate change\" or \"#ai announcement\"."New value: +"Full-text search query, e.g. \"climate change\" or \"#ai announcement\". Must not be blank." - added
Input schema / properties / query / minLengthAdded value: +1 - added
Input schema / properties / query / patternAdded value: +"\\S" - added
Input schema / properties / since / anyOfAdded value: +[ + { + "const": "", + "type": "string" + }, + { + "description": "ISO 8601 date or datetime.", + "maxLength": 32, + "pattern": "^(?:\\d{4}-(?:0?[1-9]|1[0-2])-(?:0?[1-9]|[12]\\d|3[01])|\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])T(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)?)$", + "type": "string" + } +] - changed
Input schema / properties / since / descriptionPrevious value: -"Return posts after this ISO 8601 datetime (inclusive), e.g. \"2025-01-01T00:00:00Z\"."New value: +"Return posts after this ISO 8601 date or datetime (inclusive), e.g. \"2025-01-01\" or \"2025-01-01T00:00:00Z\". Pass \"\" or omit for no lower bound." - removed
Input schema / properties / since / maxLengthRemoved value: -32 - removed
Input schema / properties / since / typeRemoved value: -"string" - added
Input schema / properties / until / anyOfAdded value: +[ + { + "const": "", + "type": "string" + }, + { + "description": "ISO 8601 date or datetime.", + "maxLength": 32, + "pattern": "^(?:\\d{4}-(?:0?[1-9]|1[0-2])-(?:0?[1-9]|[12]\\d|3[01])|\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])T(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)?)$", + "type": "string" + } +] - changed
Input schema / properties / until / descriptionPrevious value: -"Return posts before this ISO 8601 datetime (inclusive), e.g. \"2025-12-31T23:59:59Z\"."New value: +"Return posts before this ISO 8601 date or datetime (inclusive), e.g. \"2025-12-31\" or \"2025-12-31T23:59:59Z\". Pass \"\" or omit for no upper bound." - removed
Input schema / properties / until / maxLengthRemoved value: -32 - removed
Input schema / properties / until / typeRemoved value: -"string"
5 tool updates
- Changed
bsky_get_author_feed3 fields changed- added
Output schema / properties / capAdded value: +{ + "description": "The limit applied to this page.", + "type": "number" +} - added
Output schema / properties / shownAdded value: +{ + "description": "Number of posts returned on this page.", + "type": "number" +} - added
Output schema / properties / truncatedAdded value: +{ + "description": "True when more posts exist beyond this page (a cursor was returned).", + "type": "boolean" +}
- Changed
bsky_get_follows3 fields changed- added
Output schema / properties / capAdded value: +{ + "description": "The limit applied to this page.", + "type": "number" +} - added
Output schema / properties / shownAdded value: +{ + "description": "Number of actors returned on this page.", + "type": "number" +} - added
Output schema / properties / truncatedAdded value: +{ + "description": "True when more actors exist beyond this page (a cursor was returned).", + "type": "boolean" +}
- Changed
bsky_get_trending4 fields changed- added
Output schema / properties / capAdded value: +{ + "description": "The limit applied to this request.", + "type": "number" +} - added
Output schema / properties / noticeAdded value: +{ + "description": "Guidance when the result set is empty or constrained.", + "type": "string" +} - added
Output schema / properties / shownAdded value: +{ + "description": "Number of trending topics returned.", + "type": "number" +} - added
Output schema / properties / truncatedAdded value: +{ + "description": "True when the topic list was capped at the requested limit; more may exist.", + "type": "boolean" +}
- Changed
bsky_search_actors3 fields changed- added
Output schema / properties / capAdded value: +{ + "description": "The limit applied to this page.", + "type": "number" +} - added
Output schema / properties / shownAdded value: +{ + "description": "Number of actors returned on this page.", + "type": "number" +} - added
Output schema / properties / truncatedAdded value: +{ + "description": "True when more actors match than were returned on this page.", + "type": "boolean" +}
- Changed
bsky_search_posts3 fields changed- added
Output schema / properties / capAdded value: +{ + "description": "The limit applied to this page.", + "type": "number" +} - added
Output schema / properties / shownAdded value: +{ + "description": "Number of posts returned on this page.", + "type": "number" +} - added
Output schema / properties / truncatedAdded value: +{ + "description": "True when more posts match than were returned on this page.", + "type": "boolean" +}
7 tool updates
- First observed
bsky_get_author_feed - First observed
bsky_get_follows - First observed
bsky_get_post_thread - First observed
bsky_get_profile - First observed
bsky_get_trending - First observed
bsky_search_actors - First observed
bsky_search_posts
Related MCP Connectors
Bluesky post search, author feeds, profiles & threads
Search ATProto writing, annotations, identity, agents, and forum posts. 12 read-only tools.
Read public AT Protocol profiles, records, threads, backlinks and lexicons. No API key required.
Search bounded public Bluesky keywords, handles, mentions, and hashtags.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables interaction with Bluesky social network through the AT Protocol, including searching posts, fetching profiles, browsing feeds, and retrieving threads and follower data.232 npmMIT
- AlicenseAqualityAmaintenanceEnables multi-account posting, replying, threading, deletes and image/GIF/video uploads through the official X API v2, plus likes, reposts, bookmarks, follows, timelines, search, DMs, Lists and trends. It runs locally over stdio so OAuth tokens and refresh stay on the user's own machine.2419 npmMIT
- AlicenseAqualityCmaintenanceEnables LLMs to interact with the AT Protocol ecosystem, including Bluesky, through natural language. Supports public data access without authentication and full write operations with authentication.5132 npm10MIT
- FlicenseNot gradedqualityDmaintenanceEnables searching Bluesky, Substack, and Hacker News content via structured APIs with optional x402 micropayments or API key authentication.-
Glama MCP Gateway
Add one secure layer between your agents and this server.