bluesky-mcp-server
Server Details
Search posts, profiles, feeds, threads, and trending topics on Bluesky.
- Status
- Healthy
- Uptime
- 99.9% over 53 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 8 tools
Each tool targets a distinct resource or retrieval mode: profiles, author feeds, feed generators, threads, quotes, follows, trending topics, and actor search. Potentially similar tools like bsky_get_post_thread and bsky_get_post_quotes explicitly separate replies from quotes, so there is no real ambiguity.
Seven of eight tools follow the bsky_get_<resource> pattern, which is highly predictable. The one exception, bsky_search_actors, uses 'search' instead of 'get', but the deviation is minor and semantically reasonable since it is a lookup-by-query rather than a direct fetch.
Eight tools is a well-scoped size for a read-only Bluesky client. Each tool covers a meaningful retrieval concern without redundancy or feature bloat.
The server covers the core public Bluesky read workflows: profiles, author feeds, feed generators, threads, quotes, social graph, trending, and actor search. Notable gaps like post search by keyword and lists of likes/reposts are missing, but they are workable limitations rather than severe dead ends.
Available Tools
8 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 (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. | |
| 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?
Even with readOnlyHint, openWorldHint, and idempotentHint annotations, the description adds substantial behavioral context: the first three filters include reposts, a repostedBy/author field pair distinguishes authorship, media filters exclude reposts, and pinned posts arrive first and outside the limit. It also warns that limit counts reposts and directs agents to read originalPosts instead, which is valuable beyond the annotations.
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 and long, but each sentence carries a distinct behavioral or filtering fact, and the core purpose is front-loaded. A short bulleted list would improve scannability, but there is no filler or repetition.
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 five-parameter read-only tool with an output schema, the description covers ordering, filters, repost behavior, pin behavior, pagination, return fields, and the relationship to bsky_get_post_thread. Nothing an agent needs to call it correctly 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%, so the schema already documents all five parameters. The description adds interpretive value on top, especially the caveat that limit includes reposts and that include_pins adds a post outside the limit, but some of this duplicates the schema's parameter descriptions.
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 action and resource: fetching a Bluesky user's recent feed ordered newest-first. It details distinct filter modes so the tool's behavior is unmistakable, and it references bsky_get_post_thread for follow-up, distinguishing its output role from that sibling.
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 clearly conveys when to use the tool: to retrieve an actor's recent feed, with filter semantics and the pinned-post option explained. It points to bsky_search_actors for resolving bare handles and to bsky_get_post_thread for drilling into threads, but it does not explicitly contrast with bsky_get_feed or state when-not-to-use it.
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 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 (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. | |
| 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 readOnlyHint, openWorldHint, and idempotentHint, setting expectations for safety. The description adds valuable behavioral context: it explains that reposts and pinned posts are marked, that personalized feeds are inaccessible, and that pagination is cursor-based. It also notes the feed may return fewer than the limit and can be budget-capped, providing deeper insights.
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 compact, using short sentences and front-loaded information about what the tool does and how to use it. It packs in many details without being overly verbose, though it could be argued it covers a lot in one paragraph, but it remains readable and efficient.
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 complexity of the AT-URI formats and pagination, the description is remarkably complete. It covers input formats, output content, behaviors like pinned/reposted flags, and limitations on personalized feeds. The output schema exists, so return-value details are covered, and the description fills any gaps for proper 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?
The schema description coverage is 100%, so the schema already documents each parameter thoroughly, including default values and constraints. The description adds some value by explaining the feed parameter can be a handle or DID, noting the extra lookup cost, and clarifying how the cursor works, but it doesn't add much beyond the schema's own descriptions.
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 clearly states the tool reads posts from a Bluesky feed generator in ranked order, using a specific verb and resource. It distinguishes itself from siblings by mentioning bsky_get_trending, bsky_get_post_thread, and feed-related concepts, but primarily differentiates through the unique input format and purpose.
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 explicitly explains when to use this tool: to read what a trend is about, from quoted feeds, or from shared links. It also mentions that personalized feeds cannot be read and that the Discover feed is a common example, guiding the agent on appropriate contexts.
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 (1–100). Default 25. | |
| 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?
With readOnlyHint, openWorldHint, and idempotentHint already set, the description adds value beyond annotations: it warns that large graphs return only the first page, points to cursor pagination, and clarifies that the 'top' sort is Bluesky's ranking, not follower-count order. No contradiction with the readOnlyHint.
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?
A dense, compact description that front-loads the core purpose and immediately scopes the tool's output. Every sentence earns its place: the exclusions, the pagination warning, the sort nuance, and the sibling references are all necessary for correct use. The alignment is high and no repetition of schema text exists.
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?
The description covers the shape of returned data and explicitly notes what is absent, accounts for pagination and sorting, and routes the agent to sibling tools for different data. An output schema exists, so return-value detail does not need to be fully restated. Nothing essential for selecting or correctly invoking the tool is missing.
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, but the description adds meaning beyond the schema: it explains the behavior of 'top' vs 'latest', that omitting sort defaults to latest, and that cursor must be passed back with the same sort it came from. This provides genuinely useful operational semantics for each parameter.
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 the specific verb 'Fetch the social graph edges for a Bluesky account' and the two directions, distinguishing it from siblings that return feeds, posts, or single profiles. It names the resource (actor social graph) and even preempts what is not included, so an agent can confidently select it.
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 gives the alternative when the other counts/website are needed ('bsky_get_profile returns them for one account') and when a handle needs resolving ('use bsky_search_actors'). Also gives direct guidance on cursor pagination and sort 'top'. This exceeds a minimum-viable description.
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, newest first. 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. | |
| limit | No | 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. | |
| cursor | No | Opaque pagination cursor from a previous response for the same post, 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. |
| error | No | Present when the call failed. Absent on success. |
| posts | No | Posts quoting the queried post, newest first. |
| 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?
The description adds substantial behavioral detail beyond the annotations: newest-first ordering, the extra lookup cost for handles, the embed representation of the queried post, and the caveat that quoteCount is an upper bound because the counter retains quotes that left the index. It also confirms pagination support and clarifies what each result contains.
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 every sentence earns its place: purpose, sibling contrast, input formats, output shape, caveats, and pagination are all covered. It is front-loaded with the core definition and flows logically through usage details.
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 read-only, idempotent annotations, the rich schema, and the presence of an output schema, the description is complete. It covers input alternatives, output contents, pagination, a meaningful data-integrity caveat, and a handle-lookup cost — nothing an agent needs to invoke it correctly is missing.
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 descriptions are already detailed, so the baseline is 3. The description adds extra meaning by explaining where to obtain the AT-URI ('from the "uri" field of any returned post') and noting that a handle costs one extra lookup, which goes beyond the schema's syntax-only guidance.
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 precise verb and resource: 'Read the quote posts behind a Bluesky post's quoteCount.' It clearly distinguishes this from replies by explicitly noting that bsky_get_post_thread returns replies only, so an agent can identify the tool's scope without ambiguity.
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 gives explicit context for when to use this tool ('where much of the reaction to a post lives') and names the alternative for replies ('bsky_get_post_thread returns replies only'). This effectively routes an agent to the correct sibling tool based on the desired interaction type.
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, 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. | |
| parent_height | No | 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. |
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, 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. |
| 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?
The description goes far beyond the readOnly/openWorld/idempotent annotations, disclosing truncation, unreturnedReplies, truncationReason, parentChainTruncated, budget caps, and rendering behavior. It also explains that the response may be a fraction of the full conversation and that no paging exists, which is essential behavioral context.
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 with the core purpose and every sentence carries relevant behavioral information. It is quite long, but the complexity of the tool justifies the density; a small reduction in redundancy would push it to a 5.
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 and the presence of an output schema, the description is remarkably complete. It covers input sources, truncation semantics, budget-based response cutting, how to continue traversing beyond truncation, and output formatting, leaving no critical gap for correct invocation and interpretation.
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?
Even though the schema already documents all three parameters, the description adds meaningful context: accepted AT-URI formats, that bsky.app URLs work as-is, that depth does not widen the reply tree, and how parent_height interacts with parentChainTruncated. This materially improves an agent's ability to set parameters correctly.
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: 'Fetch the conversation for a post by AT-URI' and precisely scopes the behavior to the parent chain upward and reply tree downward. It also distinguishes this tool from bsky_get_post_quotes by noting quote posts are not part of a thread.
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 explicitly routes the agent to bsky_get_post_quotes for quote posts, and explains how AT-URIs are obtained from sibling tools like bsky_get_feed and bsky_get_author_feed. This gives clear context for when this tool is the right choice versus alternatives.
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, 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. |
| 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. The one link on a profile that points somewhere else — follow it before reading the bio for one. |
| 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, openWorldHint, and idempotentHint, so the safety profile is covered. The description adds value by detailing what data is returned (including verification status, moderation labels, pinned post AT-URI), which goes beyond the structured annotations. It does not contradict annotations and provides a clear, non-redundant behavioral context. It could mention rate limits or error conditions, but for a read-only, idempotent tool this is sufficient.
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 moderately long but every sentence earns its place. It starts with the core purpose, lists return fields, and then gives practical usage guidance. The structure is logical and front-loaded, with the primary action and input formats mentioned early. It is not bloated; the length is justified by the amount of useful information.
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 low complexity (one required parameter) and the existence of an output schema, the description is exceptionally complete. It covers accepted input formats, the return payload (listing many fields), and the recommended usage pattern (handle-to-DID resolution). It even provides a fallback to a sibling tool for bare names. There is nothing an agent needs to know to call this tool correctly that is missing.
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%, so the parameter 'actor' is fully documented in the schema, including accepted formats (handle, DID, @handle, URL). The tool description repeats this information and adds the context that handles and DIDs are interchangeable, but this is essentially a restatement of the schema's description. Since the schema already carries the semantic load, the description adds marginal new meaning, so a baseline score of 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 clearly states the tool's function: 'Fetch a Bluesky actor's public profile by handle or DID.' It enumerates the specific return fields (displayName, handle, DID, bio, etc.), making the purpose unambiguous and distinct from sibling tools that fetch feeds or posts. The resource and action are specific, and it even explains the use case for resolving a handle to a DID.
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 explicitly tells the agent when to use this tool: 'Use this as the first step to resolve a handle to a DID before calling tools that require a DID or AT-URI.' It also provides an exclusion in the schema description: 'A bare name without a dot is not a handle — use bsky_search_actors to resolve one.' This direct routing to an alternative is exactly what usage guidelines should do.
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 (1–25). Default 10. 25 is Bluesky's maximum. |
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?
The annotations already communicate read-only and open-world characteristics, and the description adds value by noting that the underlying endpoint is not part of Bluesky's stable lexicon and may change without notice. It also discloses that the response already includes representative accounts, so the agent knows no follow-up is needed for that information.
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 three focused sentences: one establishes the core purpose and result, one covers the comparison question and follow-up call, and one covers the stability caveat. Every sentence earns its place, and there is no filler or repeated schema content.
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 single-parameter, read-only tool with an output schema and sibling routing already described, the description is complete. It tells the agent when to call it, what it returns, how to continue to bsky_get_feed, and why the endpoint behavior may be risky. Nothing essential is missing.
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?
The only parameter, limit, is already fully described in the schema with default, minimum, maximum, and even the note that 25 is Bluesky's maximum. The description does not add parameter-specific meaning, but with 100% schema coverage 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?
The first sentence identifies a specific verb and resource: 'Fetch the current real-time trending topics on Bluesky.' It also positions the tool as the entry point for discovering what Bluesky is discussing, and clarifies that each trend is a feed that can be consumed via bsky_get_feed, preventing confusion with sibling 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?
The description explicitly tells the agent when to use the tool, using the phrase 'Entry point for what is Bluesky talking about right now.' It also routes follow-up behavior to bsky_get_feed by instructing the agent to pass the trend's feedUri, which is clear guidance on how this tool connects to its siblings.
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 (1–100). Default 25. | |
| 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, and idempotent hints. The description adds valuable behavioral context: it lists exact fields returned, explains the verification status nuance (look-alike detection), and notes what is absent. It also clarifies pagination support. No contradiction with annotations, and it enriches the agent's understanding of what to expect.
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 with the core purpose, then adds essential details about returned fields, exclusions, and usage context. Every sentence earns its place; no fluff. It's compact but thorough, and the sibling routing is woven in naturally.
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 presence of an output schema (which covers return values), the description covers all necessary guidance: what it does, what it doesn't do, when to use it, and pagination. For a search tool with three well-documented parameters and safe annotations, this is complete. No critical information is missing.
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 each parameter has a description. The description adds marginal value by explaining the 'ranked' nature of results and explicitly mentioning cursor-based pagination for browsing beyond the first page, which reinforces the cursor's purpose. It doesn't redefine params but adds usage context that schema lacks.
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 starts with 'Find Bluesky accounts by name or handle fragment,' a specific verb and resource. It distinguishes itself from siblings by explicitly listing what is not returned (follower/following/post counts, website) and pointing to bsky_get_profile for those. This makes the tool's scope unambiguous.
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 gives direct, actionable guidance: 'Use before bsky_get_profile or bsky_get_author_feed when you have a name but not a confirmed handle.' It also tells the agent what this tool is NOT for (detailed stats) and which sibling to use instead. No inference required.
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.
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
Search bounded public Bluesky keywords, handles, mentions, and hashtags.
Search ATProto writing, annotations, identity, agents, and forum posts. 12 read-only tools.
Track brand mentions & keywords on Bluesky. Sentiment, engagement, author reach. Pay per result.
Monitor Bluesky for new brand mentions, keywords and account posts. Alerts only on new results.
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.236 npmMIT
- FlicenseNot gradedqualityDmaintenanceEnables searching Bluesky, Substack, and Hacker News content via structured APIs with optional x402 micropayments or API key authentication.-
- AlicenseAqualityDmaintenanceEnables search, reading, and posting to Bluesky from any MCP client; features 11 tools (5 read, 6 write) with gated writes requiring explicit confirmation to prevent accidental publishing.1161 npmMIT
- AlicenseBqualityAmaintenanceEnables AI agents to interact with Bluesky through 41 tools covering posting, threads, replies, timelines, search, custom feeds, lists, notifications, and the social graph.45274 npmMIT
Glama MCP Gateway
Add one secure layer between your agents and this server.