@cyanheads/bluesky-mcp-server 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +346 -0
- package/CLAUDE.md +346 -0
- package/Dockerfile +99 -0
- package/LICENSE +201 -0
- package/README.md +307 -0
- package/changelog/0.1.x/0.1.1.md +24 -0
- package/changelog/template.md +127 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/bsky-profile.resource.d.ts +15 -0
- package/dist/mcp-server/resources/definitions/bsky-profile.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/bsky-profile.resource.js +54 -0
- package/dist/mcp-server/resources/definitions/bsky-profile.resource.js.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-get-author-feed.tool.d.ts +49 -0
- package/dist/mcp-server/tools/definitions/bsky-get-author-feed.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-get-author-feed.tool.js +217 -0
- package/dist/mcp-server/tools/definitions/bsky-get-author-feed.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-get-follows.tool.d.ts +43 -0
- package/dist/mcp-server/tools/definitions/bsky-get-follows.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-get-follows.tool.js +165 -0
- package/dist/mcp-server/tools/definitions/bsky-get-follows.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-get-post-thread.tool.d.ts +24 -0
- package/dist/mcp-server/tools/definitions/bsky-get-post-thread.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-get-post-thread.tool.js +166 -0
- package/dist/mcp-server/tools/definitions/bsky-get-post-thread.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-get-profile.tool.d.ts +32 -0
- package/dist/mcp-server/tools/definitions/bsky-get-profile.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-get-profile.tool.js +117 -0
- package/dist/mcp-server/tools/definitions/bsky-get-profile.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-get-trending.tool.d.ts +21 -0
- package/dist/mcp-server/tools/definitions/bsky-get-trending.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-get-trending.tool.js +81 -0
- package/dist/mcp-server/tools/definitions/bsky-get-trending.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-search-actors.tool.d.ts +27 -0
- package/dist/mcp-server/tools/definitions/bsky-search-actors.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-search-actors.tool.js +100 -0
- package/dist/mcp-server/tools/definitions/bsky-search-actors.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-search-posts.tool.d.ts +47 -0
- package/dist/mcp-server/tools/definitions/bsky-search-posts.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/bsky-search-posts.tool.js +233 -0
- package/dist/mcp-server/tools/definitions/bsky-search-posts.tool.js.map +1 -0
- package/dist/services/bluesky/bluesky-service.d.ts +68 -0
- package/dist/services/bluesky/bluesky-service.d.ts.map +1 -0
- package/dist/services/bluesky/bluesky-service.js +287 -0
- package/dist/services/bluesky/bluesky-service.js.map +1 -0
- package/dist/services/bluesky/types.d.ts +126 -0
- package/dist/services/bluesky/types.d.ts.map +1 -0
- package/dist/services/bluesky/types.js +6 -0
- package/dist/services/bluesky/types.js.map +1 -0
- package/package.json +102 -0
- package/server.json +99 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Get a Bluesky user's recent posts ordered newest-first.
|
|
3
|
+
* @module mcp-server/tools/definitions/bsky-get-author-feed
|
|
4
|
+
*/
|
|
5
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode, McpError } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { getBlueskyService } from '../../../services/bluesky/bluesky-service.js';
|
|
8
|
+
/** Embed uses passthrough so all sub-fields flow through structuredContent while format() renders the key data. */
|
|
9
|
+
const EmbedSchema = z
|
|
10
|
+
.object({})
|
|
11
|
+
.passthrough()
|
|
12
|
+
.describe('Media or link embed attached to this post. ' +
|
|
13
|
+
'type: "images" | "external" | "record" | "video" | "unknown". ' +
|
|
14
|
+
'images: array of { url, alt, aspectRatio? }. ' +
|
|
15
|
+
'external: { uri, title, description, thumb? }. ' +
|
|
16
|
+
'record: { uri, cid, text?, authorHandle? }. ' +
|
|
17
|
+
'video: { playlist?, thumbnail?, presentation?, aspectRatio? }. ' +
|
|
18
|
+
'unknown: { raw }.');
|
|
19
|
+
const PostSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
uri: z
|
|
22
|
+
.string()
|
|
23
|
+
.describe('AT-URI of the post, e.g. "at://did:plc:xxx/app.bsky.feed.post/yyy". Use with bsky_get_post_thread.'),
|
|
24
|
+
cid: z.string().describe('Content Identifier (CID) of the post record.'),
|
|
25
|
+
text: z.string().describe('Full text content of the post.'),
|
|
26
|
+
author: z
|
|
27
|
+
.object({
|
|
28
|
+
did: z
|
|
29
|
+
.string()
|
|
30
|
+
.describe('Permanent DID of the author, e.g. "did:plc:z72i7hdynmk6r22z27h6tvur".'),
|
|
31
|
+
handle: z
|
|
32
|
+
.string()
|
|
33
|
+
.describe('Human-readable handle of the author, e.g. "alice.bsky.social".'),
|
|
34
|
+
displayName: z.string().optional().describe('Display name set by the author.'),
|
|
35
|
+
avatar: z.string().optional().describe('URL of the author avatar image.'),
|
|
36
|
+
})
|
|
37
|
+
.describe('Author of this post.'),
|
|
38
|
+
replyCount: z.number().optional().describe('Number of replies to this post.'),
|
|
39
|
+
repostCount: z.number().optional().describe('Number of reposts.'),
|
|
40
|
+
likeCount: z.number().optional().describe('Number of likes.'),
|
|
41
|
+
quoteCount: z.number().optional().describe('Number of quote posts.'),
|
|
42
|
+
indexedAt: z.string().optional().describe('ISO 8601 timestamp when this post was indexed.'),
|
|
43
|
+
createdAt: z.string().optional().describe('ISO 8601 timestamp when this post was created.'),
|
|
44
|
+
labels: z
|
|
45
|
+
.array(z
|
|
46
|
+
.object({
|
|
47
|
+
val: z
|
|
48
|
+
.string()
|
|
49
|
+
.describe('Label value (content warning or moderation tag, e.g. "porn", "spam").'),
|
|
50
|
+
})
|
|
51
|
+
.describe('A moderation label.'))
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('Moderation labels on this post.'),
|
|
54
|
+
embed: EmbedSchema.optional().describe('Media or link embed attached to this post, if any.'),
|
|
55
|
+
replyToUri: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe('AT-URI of the post this is a reply to, if applicable.'),
|
|
59
|
+
})
|
|
60
|
+
.describe('A single post from the author feed.');
|
|
61
|
+
export const bskyGetAuthorFeed = tool('bsky_get_author_feed', {
|
|
62
|
+
title: 'Get Bluesky Author Feed',
|
|
63
|
+
description: "Get a Bluesky user's recent posts ordered newest-first. Filter by post type: " +
|
|
64
|
+
'"posts_with_replies" (everything), "posts_no_replies" (original posts only), ' +
|
|
65
|
+
'"posts_with_media" (posts with images or links), or "posts_and_author_threads" ' +
|
|
66
|
+
'(posts the author started). Returns posts with full text, engagement counts, embeds, ' +
|
|
67
|
+
'and AT-URIs for drilling into threads via bsky_get_post_thread. Supports cursor pagination.',
|
|
68
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
69
|
+
input: z.object({
|
|
70
|
+
actor: z
|
|
71
|
+
.string()
|
|
72
|
+
.max(253)
|
|
73
|
+
.describe('Handle (e.g. "alice.bsky.social") or DID of the author whose feed to fetch.'),
|
|
74
|
+
filter: z
|
|
75
|
+
.enum([
|
|
76
|
+
'posts_with_replies',
|
|
77
|
+
'posts_no_replies',
|
|
78
|
+
'posts_with_media',
|
|
79
|
+
'posts_and_author_threads',
|
|
80
|
+
])
|
|
81
|
+
.default('posts_no_replies')
|
|
82
|
+
.describe('Filter for post types: "posts_no_replies" for original posts only, "posts_with_replies" for everything, ' +
|
|
83
|
+
'"posts_with_media" for posts with images/links, "posts_and_author_threads" for threads the author started.'),
|
|
84
|
+
limit: z
|
|
85
|
+
.number()
|
|
86
|
+
.int()
|
|
87
|
+
.min(1)
|
|
88
|
+
.max(100)
|
|
89
|
+
.default(25)
|
|
90
|
+
.describe('Maximum number of posts to return (1–100). Default 25.'),
|
|
91
|
+
cursor: z
|
|
92
|
+
.string()
|
|
93
|
+
.max(2048)
|
|
94
|
+
.optional()
|
|
95
|
+
.describe('Opaque pagination cursor from a previous response. Omit for the first page.'),
|
|
96
|
+
}),
|
|
97
|
+
output: z.object({
|
|
98
|
+
posts: z.array(PostSchema).describe('Posts from this author, ordered newest-first.'),
|
|
99
|
+
cursor: z
|
|
100
|
+
.string()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe('Opaque cursor for the next page. Absent on the last page.'),
|
|
103
|
+
}),
|
|
104
|
+
errors: [
|
|
105
|
+
{
|
|
106
|
+
reason: 'actor_not_found',
|
|
107
|
+
code: JsonRpcErrorCode.NotFound,
|
|
108
|
+
when: 'The actor handle or DID does not resolve to an existing account.',
|
|
109
|
+
recovery: 'Verify the handle or DID, or use bsky_search_actors to find the correct actor.',
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
enrichment: {
|
|
113
|
+
totalReturned: z.number().describe('Number of posts in this response page.'),
|
|
114
|
+
},
|
|
115
|
+
async handler(input, ctx) {
|
|
116
|
+
ctx.log.info('Fetching Bluesky author feed', {
|
|
117
|
+
actor: input.actor,
|
|
118
|
+
filter: input.filter,
|
|
119
|
+
limit: input.limit,
|
|
120
|
+
});
|
|
121
|
+
let result;
|
|
122
|
+
try {
|
|
123
|
+
result = await getBlueskyService().getAuthorFeed({
|
|
124
|
+
actor: input.actor,
|
|
125
|
+
filter: input.filter,
|
|
126
|
+
limit: input.limit,
|
|
127
|
+
...(input.cursor ? { cursor: input.cursor } : {}),
|
|
128
|
+
}, ctx);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
if (err instanceof McpError) {
|
|
132
|
+
const body = err.data?.responseBody ?? '';
|
|
133
|
+
if (err.data &&
|
|
134
|
+
(body.includes('not found') || body.includes('Not Found') || body.includes('NotFound'))) {
|
|
135
|
+
throw ctx.fail('actor_not_found', `Actor not found: "${input.actor}"`, ctx.recoveryFor('actor_not_found'));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
ctx.enrich({ totalReturned: result.feed.length });
|
|
141
|
+
if (result.feed.length === 0) {
|
|
142
|
+
ctx.enrich.notice(`No posts found for actor "${input.actor}" with filter "${input.filter}".`);
|
|
143
|
+
}
|
|
144
|
+
return { posts: result.feed, ...(result.cursor ? { cursor: result.cursor } : {}) };
|
|
145
|
+
},
|
|
146
|
+
format: (result) => {
|
|
147
|
+
if (result.posts.length === 0) {
|
|
148
|
+
return [{ type: 'text', text: 'No posts found for this actor.' }];
|
|
149
|
+
}
|
|
150
|
+
const lines = result.posts.map((p) => {
|
|
151
|
+
const parts = [];
|
|
152
|
+
const author = p.author.displayName
|
|
153
|
+
? `${p.author.displayName} (@${p.author.handle})`
|
|
154
|
+
: `@${p.author.handle}`;
|
|
155
|
+
parts.push(`### ${author}`);
|
|
156
|
+
parts.push(`**AT-URI:** \`${p.uri}\` | **CID:** \`${p.cid}\``);
|
|
157
|
+
parts.push(`**Author DID:** \`${p.author.did}\``);
|
|
158
|
+
parts.push(p.text);
|
|
159
|
+
const meta = [];
|
|
160
|
+
if (p.likeCount != null)
|
|
161
|
+
meta.push(`${p.likeCount} likes`);
|
|
162
|
+
if (p.repostCount != null)
|
|
163
|
+
meta.push(`${p.repostCount} reposts`);
|
|
164
|
+
if (p.replyCount != null)
|
|
165
|
+
meta.push(`${p.replyCount} replies`);
|
|
166
|
+
if (p.quoteCount != null)
|
|
167
|
+
meta.push(`${p.quoteCount} quotes`);
|
|
168
|
+
if (meta.length)
|
|
169
|
+
parts.push(`*${meta.join(' · ')}*`);
|
|
170
|
+
if (p.createdAt)
|
|
171
|
+
parts.push(`*Created: ${p.createdAt}*`);
|
|
172
|
+
if (p.indexedAt)
|
|
173
|
+
parts.push(`*Indexed: ${p.indexedAt}*`);
|
|
174
|
+
if (p.embed) {
|
|
175
|
+
const embed = p.embed;
|
|
176
|
+
const embedType = embed.type;
|
|
177
|
+
if (embedType === 'images') {
|
|
178
|
+
const images = embed.images;
|
|
179
|
+
if (images?.length) {
|
|
180
|
+
parts.push(`📷 ${images.length} image(s): ${images.map((img) => `${img.url} [${img.alt}]`).join(', ')}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (embedType === 'external') {
|
|
184
|
+
parts.push(`🔗 [${embed.title}](${embed.uri}): ${embed.description}`);
|
|
185
|
+
}
|
|
186
|
+
else if (embedType === 'record') {
|
|
187
|
+
parts.push(`💬 Quoted post AT-URI: \`${embed.uri}\``);
|
|
188
|
+
if (embed.text)
|
|
189
|
+
parts.push(` > ${embed.text}`);
|
|
190
|
+
}
|
|
191
|
+
else if (embedType === 'video') {
|
|
192
|
+
const vid = embed;
|
|
193
|
+
const label = vid.presentation === 'gif' ? '🎞 GIF' : '🎬 Video';
|
|
194
|
+
if (vid.thumbnail)
|
|
195
|
+
parts.push(`${label}: ${vid.thumbnail}`);
|
|
196
|
+
else
|
|
197
|
+
parts.push(label);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (p.replyToUri)
|
|
201
|
+
parts.push(`↩ Reply to \`${p.replyToUri}\``);
|
|
202
|
+
if (p.author.avatar)
|
|
203
|
+
parts.push(`**Avatar:** ${p.author.avatar}`);
|
|
204
|
+
if (p.labels?.length)
|
|
205
|
+
parts.push(`**Labels:** ${p.labels.map((l) => l.val).join(', ')}`);
|
|
206
|
+
return parts.join('\n');
|
|
207
|
+
});
|
|
208
|
+
const output = lines.join('\n\n---\n\n');
|
|
209
|
+
return [
|
|
210
|
+
{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: result.cursor ? `${output}\n\n---\n*cursor: \`${result.cursor}\`*` : output,
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
//# sourceMappingURL=bsky-get-author-feed.tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bsky-get-author-feed.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/bsky-get-author-feed.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAG1E,mHAAmH;AACnH,MAAM,WAAW,GAAG,CAAC;KAClB,MAAM,CAAC,EAAE,CAAC;KACV,WAAW,EAAE;KACb,QAAQ,CACP,6CAA6C;IAC3C,gEAAgE;IAChE,+CAA+C;IAC/C,iDAAiD;IACjD,8CAA8C;IAC9C,iEAAiE;IACjE,mBAAmB,CACtB,CAAC;AAEJ,MAAM,UAAU,GAAG,CAAC;KACjB,MAAM,CAAC;IACN,GAAG,EAAE,CAAC;SACH,MAAM,EAAE;SACR,QAAQ,CACP,oGAAoG,CACrG;IACH,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8CAA8C,CAAC;IACxE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gCAAgC,CAAC;IAC3D,MAAM,EAAE,CAAC;SACN,MAAM,CAAC;QACN,GAAG,EAAE,CAAC;aACH,MAAM,EAAE;aACR,QAAQ,CAAC,uEAAuE,CAAC;QACpF,MAAM,EAAE,CAAC;aACN,MAAM,EAAE;aACR,QAAQ,CAAC,gEAAgE,CAAC;QAC7E,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;QAC9E,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;KAC1E,CAAC;SACD,QAAQ,CAAC,sBAAsB,CAAC;IACnC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IAC7E,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;IACjE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IAC7D,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;IACpE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;IAC3F,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;IAC3F,MAAM,EAAE,CAAC;SACN,KAAK,CACJ,CAAC;SACE,MAAM,CAAC;QACN,GAAG,EAAE,CAAC;aACH,MAAM,EAAE;aACR,QAAQ,CAAC,uEAAuE,CAAC;KACrF,CAAC;SACD,QAAQ,CAAC,qBAAqB,CAAC,CACnC;SACA,QAAQ,EAAE;SACV,QAAQ,CAAC,iCAAiC,CAAC;IAC9C,KAAK,EAAE,WAAW,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oDAAoD,CAAC;IAC5F,UAAU,EAAE,CAAC;SACV,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,uDAAuD,CAAC;CACrE,CAAC;KACD,QAAQ,CAAC,qCAAqC,CAAC,CAAC;AAEnD,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC,sBAAsB,EAAE;IAC5D,KAAK,EAAE,yBAAyB;IAChC,WAAW,EACT,+EAA+E;QAC/E,+EAA+E;QAC/E,iFAAiF;QACjF,uFAAuF;QACvF,6FAA6F;IAC/F,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;IAC9E,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACd,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,CAAC,6EAA6E,CAAC;QAC1F,MAAM,EAAE,CAAC;aACN,IAAI,CAAC;YACJ,oBAAoB;YACpB,kBAAkB;YAClB,kBAAkB;YAClB,0BAA0B;SAC3B,CAAC;aACD,OAAO,CAAC,kBAAkB,CAAC;aAC3B,QAAQ,CACP,0GAA0G;YACxG,4GAA4G,CAC/G;QACH,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,GAAG,EAAE;aACL,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,GAAG,CAAC;aACR,OAAO,CAAC,EAAE,CAAC;aACX,QAAQ,CAAC,wDAAwD,CAAC;QACrE,MAAM,EAAE,CAAC;aACN,MAAM,EAAE;aACR,GAAG,CAAC,IAAI,CAAC;aACT,QAAQ,EAAE;aACV,QAAQ,CAAC,6EAA6E,CAAC;KAC3F,CAAC;IACF,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,+CAA+C,CAAC;QACpF,MAAM,EAAE,CAAC;aACN,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,2DAA2D,CAAC;KACzE,CAAC;IAEF,MAAM,EAAE;QACN;YACE,MAAM,EAAE,iBAAiB;YACzB,IAAI,EAAE,gBAAgB,CAAC,QAAQ;YAC/B,IAAI,EAAE,kEAAkE;YACxE,QAAQ,EAAE,gFAAgF;SAC3F;KACF;IAED,UAAU,EAAE;QACV,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC;KAC7E;IAED,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG;QACtB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,8BAA8B,EAAE;YAC3C,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK;SACnB,CAAC,CAAC;QACH,IAAI,MAAwB,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,iBAAiB,EAAE,CAAC,aAAa,CAC9C;gBACE,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAClD,EACD,GAAG,CACJ,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;gBAC5B,MAAM,IAAI,GAAI,GAAG,CAAC,IAA8C,EAAE,YAAY,IAAI,EAAE,CAAC;gBACrF,IACE,GAAG,CAAC,IAAI;oBACR,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,EACvF,CAAC;oBACD,MAAM,GAAG,CAAC,IAAI,CACZ,iBAAiB,EACjB,qBAAqB,KAAK,CAAC,KAAK,GAAG,EACnC,GAAG,CAAC,WAAW,CAAC,iBAAiB,CAAC,CACnC,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,GAAG,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAClD,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,6BAA6B,KAAK,CAAC,KAAK,kBAAkB,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;QAChG,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACrF,CAAC;IAED,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;QACjB,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,gCAAgC,EAAE,CAAC,CAAC;QACpE,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACnC,MAAM,KAAK,GAAa,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,WAAW;gBACjC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,WAAW,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG;gBACjD,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC1B,KAAK,CAAC,IAAI,CAAC,OAAO,MAAM,EAAE,CAAC,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,GAAG,mBAAmB,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;YAC/D,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;YAClD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACnB,MAAM,IAAI,GAAa,EAAE,CAAC;YAC1B,IAAI,CAAC,CAAC,SAAS,IAAI,IAAI;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC;YAC3D,IAAI,CAAC,CAAC,WAAW,IAAI,IAAI;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,UAAU,CAAC,CAAC;YACjE,IAAI,CAAC,CAAC,UAAU,IAAI,IAAI;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,UAAU,UAAU,CAAC,CAAC;YAC/D,IAAI,CAAC,CAAC,UAAU,IAAI,IAAI;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,UAAU,SAAS,CAAC,CAAC;YAC9D,IAAI,IAAI,CAAC,MAAM;gBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACrD,IAAI,CAAC,CAAC,SAAS;gBAAE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC;YACzD,IAAI,CAAC,CAAC,SAAS;gBAAE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC;YACzD,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;gBACZ,MAAM,KAAK,GAAG,CAAC,CAAC,KAAgC,CAAC;gBACjD,MAAM,SAAS,GAAG,KAAK,CAAC,IAA0B,CAAC;gBACnD,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;oBAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAyD,CAAC;oBAC/E,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;wBACnB,KAAK,CAAC,IAAI,CACR,MAAM,MAAM,CAAC,MAAM,cAAc,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC7F,CAAC;oBACJ,CAAC;gBACH,CAAC;qBAAM,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;oBACpC,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,GAAG,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;gBACxE,CAAC;qBAAM,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;oBAClC,KAAK,CAAC,IAAI,CAAC,4BAA4B,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;oBACtD,IAAI,KAAK,CAAC,IAAI;wBAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;gBACnD,CAAC;qBAAM,IAAI,SAAS,KAAK,OAAO,EAAE,CAAC;oBACjC,MAAM,GAAG,GAAG,KAAyE,CAAC;oBACtF,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;oBACjE,IAAI,GAAG,CAAC,SAAS;wBAAE,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,KAAK,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;;wBACvD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;YACD,IAAI,CAAC,CAAC,UAAU;gBAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC;YAC/D,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM;gBAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;YAClE,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM;gBAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACzF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACzC,OAAO;YACL;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,uBAAuB,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,MAAM;aAClF;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Fetch social graph edges for a Bluesky account — followers or following.
|
|
3
|
+
* @module mcp-server/tools/definitions/bsky-get-follows
|
|
4
|
+
*/
|
|
5
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
export declare const bskyGetFollows: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
8
|
+
actor: z.ZodString;
|
|
9
|
+
direction: z.ZodEnum<{
|
|
10
|
+
followers: "followers";
|
|
11
|
+
following: "following";
|
|
12
|
+
}>;
|
|
13
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
14
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
15
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
16
|
+
actors: z.ZodArray<z.ZodObject<{
|
|
17
|
+
did: z.ZodString;
|
|
18
|
+
handle: z.ZodString;
|
|
19
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
20
|
+
description: z.ZodOptional<z.ZodString>;
|
|
21
|
+
avatar: z.ZodOptional<z.ZodString>;
|
|
22
|
+
followersCount: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
labels: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
24
|
+
val: z.ZodString;
|
|
25
|
+
}, z.core.$strip>>>;
|
|
26
|
+
}, z.core.$strip>>;
|
|
27
|
+
subject: z.ZodObject<{
|
|
28
|
+
did: z.ZodString;
|
|
29
|
+
handle: z.ZodString;
|
|
30
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
31
|
+
followersCount: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
followsCount: z.ZodOptional<z.ZodNumber>;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
35
|
+
}, z.core.$strip>, readonly [{
|
|
36
|
+
readonly reason: "actor_not_found";
|
|
37
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
38
|
+
readonly when: "The actor handle or DID does not resolve to an existing account.";
|
|
39
|
+
readonly recovery: "Verify the actor handle or DID, or use bsky_search_actors to confirm the handle.";
|
|
40
|
+
}], {
|
|
41
|
+
readonly totalReturned: z.ZodNumber;
|
|
42
|
+
}>;
|
|
43
|
+
//# sourceMappingURL=bsky-get-follows.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bsky-get-follows.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/bsky-get-follows.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AA2B3E,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqJzB,CAAC"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Fetch social graph edges for a Bluesky account — followers or following.
|
|
3
|
+
* @module mcp-server/tools/definitions/bsky-get-follows
|
|
4
|
+
*/
|
|
5
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode, McpError } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { getBlueskyService } from '../../../services/bluesky/bluesky-service.js';
|
|
8
|
+
const ActorSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
did: z.string().describe('Decentralized Identifier of the actor.'),
|
|
11
|
+
handle: z.string().describe('Human-readable handle, e.g. "alice.bsky.social".'),
|
|
12
|
+
displayName: z.string().optional().describe('Display name set by the user.'),
|
|
13
|
+
description: z.string().optional().describe('Biography / about text.'),
|
|
14
|
+
avatar: z.string().optional().describe('Avatar image URL.'),
|
|
15
|
+
followersCount: z.number().optional().describe('Number of followers.'),
|
|
16
|
+
labels: z
|
|
17
|
+
.array(z
|
|
18
|
+
.object({
|
|
19
|
+
val: z
|
|
20
|
+
.string()
|
|
21
|
+
.describe('Label value (content warning or moderation tag, e.g. "porn", "spam").'),
|
|
22
|
+
})
|
|
23
|
+
.describe('A moderation label.'))
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Moderation labels.'),
|
|
26
|
+
})
|
|
27
|
+
.describe('A Bluesky actor in the social graph.');
|
|
28
|
+
export const bskyGetFollows = tool('bsky_get_follows', {
|
|
29
|
+
title: 'Get Bluesky Social Graph',
|
|
30
|
+
description: 'Fetch the social graph edges for a Bluesky account — who follows them, or who they follow. ' +
|
|
31
|
+
'Returns paginated actor profiles (handle, DID, displayName, bio, follower count) plus a summary ' +
|
|
32
|
+
'of the subject account. Accounts with large social graphs return only the first page; use ' +
|
|
33
|
+
'cursor pagination to walk through the full list.',
|
|
34
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
35
|
+
input: z.object({
|
|
36
|
+
actor: z
|
|
37
|
+
.string()
|
|
38
|
+
.max(253)
|
|
39
|
+
.describe('Handle (e.g. "alice.bsky.social") or DID of the account to query.'),
|
|
40
|
+
direction: z
|
|
41
|
+
.enum(['followers', 'following'])
|
|
42
|
+
.describe('"followers" returns accounts that follow this actor. "following" returns accounts this actor follows.'),
|
|
43
|
+
limit: z
|
|
44
|
+
.number()
|
|
45
|
+
.int()
|
|
46
|
+
.min(1)
|
|
47
|
+
.max(100)
|
|
48
|
+
.default(25)
|
|
49
|
+
.describe('Maximum number of actors to return per page (1–100). Default 25.'),
|
|
50
|
+
cursor: z
|
|
51
|
+
.string()
|
|
52
|
+
.max(2048)
|
|
53
|
+
.optional()
|
|
54
|
+
.describe('Opaque pagination cursor from a previous response. Omit for the first page.'),
|
|
55
|
+
}),
|
|
56
|
+
output: z.object({
|
|
57
|
+
actors: z.array(ActorSchema).describe('Actors in the requested direction of the social graph.'),
|
|
58
|
+
subject: z
|
|
59
|
+
.object({
|
|
60
|
+
did: z.string().describe('Permanent DID of the queried account.'),
|
|
61
|
+
handle: z.string().describe('Human-readable handle of the queried account.'),
|
|
62
|
+
displayName: z.string().optional().describe('Subject display name.'),
|
|
63
|
+
followersCount: z.number().optional().describe("Subject's follower count."),
|
|
64
|
+
followsCount: z.number().optional().describe("Subject's following count."),
|
|
65
|
+
})
|
|
66
|
+
.describe('Profile summary of the queried actor.'),
|
|
67
|
+
cursor: z
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe('Opaque cursor for the next page. Absent on the last page.'),
|
|
71
|
+
}),
|
|
72
|
+
errors: [
|
|
73
|
+
{
|
|
74
|
+
reason: 'actor_not_found',
|
|
75
|
+
code: JsonRpcErrorCode.NotFound,
|
|
76
|
+
when: 'The actor handle or DID does not resolve to an existing account.',
|
|
77
|
+
recovery: 'Verify the actor handle or DID, or use bsky_search_actors to confirm the handle.',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
enrichment: {
|
|
81
|
+
totalReturned: z.number().describe('Number of actors in this response page.'),
|
|
82
|
+
},
|
|
83
|
+
async handler(input, ctx) {
|
|
84
|
+
ctx.log.info('Fetching Bluesky social graph', {
|
|
85
|
+
actor: input.actor,
|
|
86
|
+
direction: input.direction,
|
|
87
|
+
limit: input.limit,
|
|
88
|
+
});
|
|
89
|
+
const params = {
|
|
90
|
+
actor: input.actor,
|
|
91
|
+
limit: input.limit,
|
|
92
|
+
...(input.cursor ? { cursor: input.cursor } : {}),
|
|
93
|
+
};
|
|
94
|
+
let result;
|
|
95
|
+
try {
|
|
96
|
+
result =
|
|
97
|
+
input.direction === 'followers'
|
|
98
|
+
? await getBlueskyService().getFollowers(params, ctx)
|
|
99
|
+
: await getBlueskyService().getFollows(params, ctx);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (err instanceof McpError) {
|
|
103
|
+
const body = err.data?.responseBody ?? '';
|
|
104
|
+
if (err.data &&
|
|
105
|
+
(body.includes('not found') || body.includes('Not Found') || body.includes('NotFound'))) {
|
|
106
|
+
throw ctx.fail('actor_not_found', `Actor not found: "${input.actor}"`, ctx.recoveryFor('actor_not_found'));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
ctx.enrich({ totalReturned: result.actors.length });
|
|
112
|
+
if (result.actors.length === 0) {
|
|
113
|
+
ctx.enrich.notice(`No ${input.direction} found for actor "${input.actor}".`);
|
|
114
|
+
}
|
|
115
|
+
const { followersCount, followsCount, did, handle, displayName } = result.subject;
|
|
116
|
+
return {
|
|
117
|
+
actors: result.actors,
|
|
118
|
+
subject: {
|
|
119
|
+
did,
|
|
120
|
+
handle,
|
|
121
|
+
...(displayName ? { displayName } : {}),
|
|
122
|
+
...(followersCount != null ? { followersCount } : {}),
|
|
123
|
+
...(followsCount != null ? { followsCount } : {}),
|
|
124
|
+
},
|
|
125
|
+
...(result.cursor ? { cursor: result.cursor } : {}),
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
format: (result) => {
|
|
129
|
+
const subjectLabel = result.subject.displayName
|
|
130
|
+
? `${result.subject.displayName} (@${result.subject.handle})`
|
|
131
|
+
: `@${result.subject.handle}`;
|
|
132
|
+
const header = [`## Subject: ${subjectLabel}`];
|
|
133
|
+
header.push(`**DID:** \`${result.subject.did}\``);
|
|
134
|
+
if (result.subject.followersCount != null)
|
|
135
|
+
header.push(`Followers: ${result.subject.followersCount.toLocaleString()}`);
|
|
136
|
+
if (result.subject.followsCount != null)
|
|
137
|
+
header.push(`Following: ${result.subject.followsCount.toLocaleString()}`);
|
|
138
|
+
if (result.actors.length === 0) {
|
|
139
|
+
return [{ type: 'text', text: `${header.join('\n')}\n\n*No accounts found.*` }];
|
|
140
|
+
}
|
|
141
|
+
const actorLines = result.actors.map((a) => {
|
|
142
|
+
const parts = [`### @${a.handle}`];
|
|
143
|
+
parts.push(`**DID:** \`${a.did}\``);
|
|
144
|
+
if (a.displayName)
|
|
145
|
+
parts.push(`**Name:** ${a.displayName}`);
|
|
146
|
+
if (a.description)
|
|
147
|
+
parts.push(a.description);
|
|
148
|
+
if (a.followersCount != null)
|
|
149
|
+
parts.push(`**Followers:** ${a.followersCount.toLocaleString()}`);
|
|
150
|
+
if (a.labels?.length)
|
|
151
|
+
parts.push(`**Labels:** ${a.labels.map((l) => l.val).join(', ')}`);
|
|
152
|
+
if (a.avatar)
|
|
153
|
+
parts.push(`**Avatar:** ${a.avatar}`);
|
|
154
|
+
return parts.join('\n');
|
|
155
|
+
});
|
|
156
|
+
const footer = result.cursor ? `\n\n---\n*cursor: \`${result.cursor}\`*` : '';
|
|
157
|
+
return [
|
|
158
|
+
{
|
|
159
|
+
type: 'text',
|
|
160
|
+
text: `${header.join('\n')}\n\n---\n\n${actorLines.join('\n\n')}${footer}`,
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
//# sourceMappingURL=bsky-get-follows.tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bsky-get-follows.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/bsky-get-follows.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Fetch a full Bluesky post conversation thread by AT-URI.
|
|
3
|
+
* @module mcp-server/tools/definitions/bsky-get-post-thread
|
|
4
|
+
*/
|
|
5
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
export declare const bskyGetPostThread: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
8
|
+
uri: z.ZodString;
|
|
9
|
+
depth: z.ZodDefault<z.ZodNumber>;
|
|
10
|
+
parent_height: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
12
|
+
thread: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
13
|
+
}, z.core.$strip>, readonly [{
|
|
14
|
+
readonly reason: "invalid_at_uri";
|
|
15
|
+
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
16
|
+
readonly when: "The uri parameter is not a valid AT-URI (at://<did>/<collection>/<rkey>).";
|
|
17
|
+
readonly recovery: "AT-URIs come from the \"uri\" field of posts returned by bsky_search_posts or bsky_get_author_feed.";
|
|
18
|
+
}, {
|
|
19
|
+
readonly reason: "post_not_found";
|
|
20
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
21
|
+
readonly when: "The AT-URI is valid format but the post was deleted or never existed.";
|
|
22
|
+
readonly recovery: "Verify the AT-URI or use bsky_search_posts to find the correct post.";
|
|
23
|
+
}], undefined>;
|
|
24
|
+
//# sourceMappingURL=bsky-get-post-thread.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bsky-get-post-thread.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/bsky-get-post-thread.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AAoD3E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;cAmI5B,CAAC"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Fetch a full Bluesky post conversation thread by AT-URI.
|
|
3
|
+
* @module mcp-server/tools/definitions/bsky-get-post-thread
|
|
4
|
+
*/
|
|
5
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode, McpError } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { getBlueskyService } from '../../../services/bluesky/bluesky-service.js';
|
|
8
|
+
/** @internal Recursively format a thread tree into readable markdown lines. */
|
|
9
|
+
function formatThreadNode(node, depth, lines) {
|
|
10
|
+
const indent = ' '.repeat(depth);
|
|
11
|
+
if (node.notFound) {
|
|
12
|
+
lines.push(`${indent}*[Post not found or deleted]*`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (node.truncated) {
|
|
16
|
+
lines.push(`${indent}*[More replies — use a deeper depth to load them]*`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const p = node.post;
|
|
20
|
+
const author = p.author.displayName
|
|
21
|
+
? `${p.author.displayName} (@${p.author.handle})`
|
|
22
|
+
: `@${p.author.handle}`;
|
|
23
|
+
lines.push(`${indent}### ${author}`);
|
|
24
|
+
lines.push(`${indent}**AT-URI:** \`${p.uri}\``);
|
|
25
|
+
lines.push(`${indent}${p.text}`);
|
|
26
|
+
const meta = [];
|
|
27
|
+
if (p.likeCount != null)
|
|
28
|
+
meta.push(`${p.likeCount} likes`);
|
|
29
|
+
if (p.repostCount != null)
|
|
30
|
+
meta.push(`${p.repostCount} reposts`);
|
|
31
|
+
if (p.replyCount != null)
|
|
32
|
+
meta.push(`${p.replyCount} replies`);
|
|
33
|
+
if (meta.length)
|
|
34
|
+
lines.push(`${indent}*${meta.join(' · ')}*`);
|
|
35
|
+
if (p.createdAt)
|
|
36
|
+
lines.push(`${indent}*${p.createdAt}*`);
|
|
37
|
+
if (p.labels?.length)
|
|
38
|
+
lines.push(`${indent}**Labels:** ${p.labels.map((l) => l.val).join(', ')}`);
|
|
39
|
+
if (node.replies?.length) {
|
|
40
|
+
lines.push(`${indent}---`);
|
|
41
|
+
for (const reply of node.replies) {
|
|
42
|
+
formatThreadNode(reply, depth + 1, lines);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Thread node schema — uses passthrough so all post fields (uri, cid, text, author, engagement counts,
|
|
48
|
+
* createdAt, labels, embed, replyToUri) and thread structure (parent, replies, truncated, notFound)
|
|
49
|
+
* flow through structuredContent without format-parity constraints on the recursive tree shape.
|
|
50
|
+
*/
|
|
51
|
+
const ThreadNodeSchema = z
|
|
52
|
+
.object({})
|
|
53
|
+
.passthrough()
|
|
54
|
+
.describe('Recursive thread node. Each node has: ' +
|
|
55
|
+
'post: { uri, cid, text, author: { did, handle, displayName?, avatar? }, replyCount?, repostCount?, likeCount?, quoteCount?, indexedAt?, createdAt?, labels?, embed?, replyToUri? }. ' +
|
|
56
|
+
'parent?: parent thread node. replies?: array of child thread nodes. ' +
|
|
57
|
+
'truncated?: true when the API cut off deeper replies. notFound?: true when the post was deleted.');
|
|
58
|
+
export const bskyGetPostThread = tool('bsky_get_post_thread', {
|
|
59
|
+
title: 'Get Bluesky Post Thread',
|
|
60
|
+
description: 'Fetch the full conversation for a post by AT-URI — the parent chain upward and the reply tree downward. ' +
|
|
61
|
+
'Enter the thread at any point and traverse the full discussion. ' +
|
|
62
|
+
'AT-URIs have the format "at://<did>/<collection>/<rkey>" and are returned by bsky_search_posts and ' +
|
|
63
|
+
'bsky_get_author_feed in the "uri" field of each post. ' +
|
|
64
|
+
'Returns the root post, parent chain, and nested replies with per-post author and engagement data. ' +
|
|
65
|
+
'"truncated: true" on a reply node means there are more replies below — increase depth to load them.',
|
|
66
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
67
|
+
input: z.object({
|
|
68
|
+
uri: z
|
|
69
|
+
.string()
|
|
70
|
+
.max(2048)
|
|
71
|
+
.describe('AT-URI of the post to fetch, e.g. "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/abc123". ' +
|
|
72
|
+
'Obtain from bsky_search_posts or bsky_get_author_feed.'),
|
|
73
|
+
depth: z
|
|
74
|
+
.number()
|
|
75
|
+
.int()
|
|
76
|
+
.min(0)
|
|
77
|
+
.max(1000)
|
|
78
|
+
.default(6)
|
|
79
|
+
.describe('How many levels of replies to include in the reply tree. Default 6.'),
|
|
80
|
+
parent_height: z
|
|
81
|
+
.number()
|
|
82
|
+
.int()
|
|
83
|
+
.min(0)
|
|
84
|
+
.max(1000)
|
|
85
|
+
.default(80)
|
|
86
|
+
.describe('How many parent posts to include in the parent chain above the target post. Default 80.'),
|
|
87
|
+
}),
|
|
88
|
+
output: z.object({
|
|
89
|
+
thread: ThreadNodeSchema.describe('The conversation thread rooted at the requested post.'),
|
|
90
|
+
}),
|
|
91
|
+
errors: [
|
|
92
|
+
{
|
|
93
|
+
reason: 'invalid_at_uri',
|
|
94
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
95
|
+
when: 'The uri parameter is not a valid AT-URI (at://<did>/<collection>/<rkey>).',
|
|
96
|
+
recovery: 'AT-URIs come from the "uri" field of posts returned by bsky_search_posts or bsky_get_author_feed.',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
reason: 'post_not_found',
|
|
100
|
+
code: JsonRpcErrorCode.NotFound,
|
|
101
|
+
when: 'The AT-URI is valid format but the post was deleted or never existed.',
|
|
102
|
+
recovery: 'Verify the AT-URI or use bsky_search_posts to find the correct post.',
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
async handler(input, ctx) {
|
|
106
|
+
ctx.log.info('Fetching Bluesky post thread', { uri: input.uri, depth: input.depth });
|
|
107
|
+
// Validate AT-URI format before hitting the API
|
|
108
|
+
if (!input.uri.startsWith('at://')) {
|
|
109
|
+
throw ctx.fail('invalid_at_uri', `Invalid AT-URI: "${input.uri}" must start with "at://"`, ctx.recoveryFor('invalid_at_uri'));
|
|
110
|
+
}
|
|
111
|
+
let thread;
|
|
112
|
+
try {
|
|
113
|
+
thread = await getBlueskyService().getPostThread({ uri: input.uri, depth: input.depth, parentHeight: input.parent_height }, ctx);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err instanceof McpError) {
|
|
117
|
+
const body = err.data?.responseBody ?? '';
|
|
118
|
+
if (err.data &&
|
|
119
|
+
(body.includes('NotFound') ||
|
|
120
|
+
body.includes('not found') ||
|
|
121
|
+
body.includes('Not Found') ||
|
|
122
|
+
body.includes('Post not found'))) {
|
|
123
|
+
throw ctx.fail('post_not_found', `Post not found: "${input.uri}"`, ctx.recoveryFor('post_not_found'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
return { thread };
|
|
129
|
+
},
|
|
130
|
+
format: (result) => {
|
|
131
|
+
const thread = result.thread;
|
|
132
|
+
if (!thread || thread.notFound) {
|
|
133
|
+
return [{ type: 'text', text: '*Post not found or deleted.*' }];
|
|
134
|
+
}
|
|
135
|
+
const lines = ['# Thread'];
|
|
136
|
+
// Render parent chain first (walking up)
|
|
137
|
+
if (thread.parent) {
|
|
138
|
+
lines.push('## Parent chain');
|
|
139
|
+
const parents = [];
|
|
140
|
+
let cur = thread.parent;
|
|
141
|
+
while (cur) {
|
|
142
|
+
parents.unshift(cur);
|
|
143
|
+
cur = cur.parent;
|
|
144
|
+
}
|
|
145
|
+
for (const p of parents) {
|
|
146
|
+
const { replies: _r, ...pWithoutReplies } = p;
|
|
147
|
+
formatThreadNode(pWithoutReplies, 0, lines);
|
|
148
|
+
lines.push('');
|
|
149
|
+
}
|
|
150
|
+
lines.push('---');
|
|
151
|
+
}
|
|
152
|
+
lines.push('## This post');
|
|
153
|
+
const { parent: _p, ...threadWithoutParent } = thread;
|
|
154
|
+
formatThreadNode(threadWithoutParent, 0, lines);
|
|
155
|
+
if (thread.replies?.length) {
|
|
156
|
+
lines.push('');
|
|
157
|
+
lines.push('## Replies');
|
|
158
|
+
for (const reply of thread.replies) {
|
|
159
|
+
formatThreadNode(reply, 0, lines);
|
|
160
|
+
lines.push('');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
//# sourceMappingURL=bsky-get-post-thread.tool.js.map
|