Skip to main content
Glama

bluesky-mcp-server

Get Bluesky Author Feed

bsky_get_author_feed
Read-onlyIdempotent

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.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actorYesHandle (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.
limitNoMaximum 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.
cursorNoOpaque pagination cursor from a previous response for the same actor, passed back unchanged. Omit for the first page.
filterNoFilter 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_pinsNoAlso 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

TableJSON Schema
NameRequiredDescriptionDefault
capNoThe limit applied to this page.
errorNoPresent when the call failed. Absent on success.
postsNoFeed 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.
shownNoNumber of posts returned on this page.
cursorNoOpaque cursor for the next page. Absent on the last page.
noticeNoGuidance when the result set is empty or constrained.
repostsNoHow 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".
truncatedNoTrue when more posts exist beyond this page (a cursor was returned).
budgetCappedNoTrue 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.
originalPostsNoHow 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.
totalReturnedNoNumber of posts in this response page.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed3 schema fields changed
    • changedInput schema / properties / limit / description
      Previous 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."
    • addedOutput schema / properties / budgetCapped
      Added 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"
      +}
    • addedOutput schema / properties / posts / items / properties / author / properties / verification
      Added 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"
      +}
  2. Changed12 schema fields changed
    • changedInput schema / properties / actor / description
      Previous 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."
    • changedInput schema / properties / actor / maxLength
      Previous value: -253New value: +2048
    • changedInput schema / properties / actor / pattern
      Previous 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._-])\\/?(?:[?#].*)?)$"
    • changedInput schema / properties / cursor / description
      Previous 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."
    • changedInput schema / properties / filter / description
      Previous 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."
    • changedInput schema / properties / filter / enum
      Previous 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"
      +]
    • addedInput schema / properties / include_pins
      Added 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"
      +}
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous 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."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "actor_not_found"
      -]New value: +[
      +  "actor_not_found",
      +  "invalid_cursor"
      +]
    • changedOutput schema / properties / originalPosts / description
      Previous 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."
    • addedOutput schema / properties / posts / items / properties / pinned
      Added 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"
      +}
    • changedOutput schema / properties / posts / items / properties / quoteCount / description
      Previous 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."
  3. Changed1 schema field changed
    • changedOutput schema / properties / posts / items / properties / embed / description
      Previous 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."
  4. Changed6 schema fields changed
    • changedInput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedInput schema / additionalProperties
      Added value: +false
    • changedOutput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedOutput schema / anyOf
      Added value: +[
      +  {
      +    "not": {
      +      "required": [
      +        "error"
      +      ]
      +    },
      +    "required": [
      +      "posts",
      +      "totalReturned"
      +    ]
      +  },
      +  {
      +    "required": [
      +      "error"
      +    ]
      +  }
      +]
    • addedOutput schema / properties / error
      Added 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"
      +}
    • removedOutput schema / required
      Removed value: -[
      -  "posts",
      -  "totalReturned"
      -]
  5. Changed4 schema fields changed
    • changedOutput schema / properties / posts / items / properties / embed / description
      Previous 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."
    • changedOutput schema / properties / posts / items / properties / labels / items / description
      Previous value: -"A moderation label."New value: +"A moderation label applied by the AppView or a labeler service."
    • addedOutput schema / properties / posts / items / properties / labels / items / properties / cts
      Added value: +{
      +  "description": "ISO 8601 timestamp when the label was applied.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / posts / items / properties / labels / items / properties / src
      Added 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. Changed3 schema fields changed
    • addedOutput schema / properties / originalPosts
      Added 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"
      +}
    • changedOutput schema / properties / posts / items / properties / embed / description
      Previous 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."
    • addedOutput schema / properties / reposts
      Added 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"
      +}
  7. Changed7 schema fields changed
    • changedInput schema / properties / filter / description
      Previous 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."
    • changedOutput schema / properties / posts / description
      Previous 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."
    • changedOutput schema / properties / posts / items / description
      Previous 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."
    • changedOutput schema / properties / posts / items / properties / embed / description
      Previous 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."
    • addedOutput schema / properties / posts / items / properties / replyRootUri
      Added 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"
      +}
    • addedOutput schema / properties / posts / items / properties / repostedAt
      Added value: +{
      +  "description": "ISO 8601 timestamp of the repost. Present only on reposted items.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / posts / items / properties / repostedBy
      Added 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"
      +}
  8. Changed3 schema fields changed
    • changedInput schema / properties / actor / description
      Previous 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."
    • addedInput schema / properties / actor / minLength
      Added value: +1
    • addedInput schema / properties / actor / pattern
      Added 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._-])$"
  9. Changed3 schema fields changed
    • addedOutput schema / properties / cap
      Added value: +{
      +  "description": "The limit applied to this page.",
      +  "type": "number"
      +}
    • addedOutput schema / properties / shown
      Added value: +{
      +  "description": "Number of posts returned on this page.",
      +  "type": "number"
      +}
    • addedOutput schema / properties / truncated
      Added value: +{
      +  "description": "True when more posts exist beyond this page (a cursor was returned).",
      +  "type": "boolean"
      +}
  10. First observed

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.