@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.
Files changed (53) hide show
  1. package/AGENTS.md +346 -0
  2. package/CLAUDE.md +346 -0
  3. package/Dockerfile +99 -0
  4. package/LICENSE +201 -0
  5. package/README.md +307 -0
  6. package/changelog/0.1.x/0.1.1.md +24 -0
  7. package/changelog/template.md +127 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +43 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/mcp-server/resources/definitions/bsky-profile.resource.d.ts +15 -0
  13. package/dist/mcp-server/resources/definitions/bsky-profile.resource.d.ts.map +1 -0
  14. package/dist/mcp-server/resources/definitions/bsky-profile.resource.js +54 -0
  15. package/dist/mcp-server/resources/definitions/bsky-profile.resource.js.map +1 -0
  16. package/dist/mcp-server/tools/definitions/bsky-get-author-feed.tool.d.ts +49 -0
  17. package/dist/mcp-server/tools/definitions/bsky-get-author-feed.tool.d.ts.map +1 -0
  18. package/dist/mcp-server/tools/definitions/bsky-get-author-feed.tool.js +217 -0
  19. package/dist/mcp-server/tools/definitions/bsky-get-author-feed.tool.js.map +1 -0
  20. package/dist/mcp-server/tools/definitions/bsky-get-follows.tool.d.ts +43 -0
  21. package/dist/mcp-server/tools/definitions/bsky-get-follows.tool.d.ts.map +1 -0
  22. package/dist/mcp-server/tools/definitions/bsky-get-follows.tool.js +165 -0
  23. package/dist/mcp-server/tools/definitions/bsky-get-follows.tool.js.map +1 -0
  24. package/dist/mcp-server/tools/definitions/bsky-get-post-thread.tool.d.ts +24 -0
  25. package/dist/mcp-server/tools/definitions/bsky-get-post-thread.tool.d.ts.map +1 -0
  26. package/dist/mcp-server/tools/definitions/bsky-get-post-thread.tool.js +166 -0
  27. package/dist/mcp-server/tools/definitions/bsky-get-post-thread.tool.js.map +1 -0
  28. package/dist/mcp-server/tools/definitions/bsky-get-profile.tool.d.ts +32 -0
  29. package/dist/mcp-server/tools/definitions/bsky-get-profile.tool.d.ts.map +1 -0
  30. package/dist/mcp-server/tools/definitions/bsky-get-profile.tool.js +117 -0
  31. package/dist/mcp-server/tools/definitions/bsky-get-profile.tool.js.map +1 -0
  32. package/dist/mcp-server/tools/definitions/bsky-get-trending.tool.d.ts +21 -0
  33. package/dist/mcp-server/tools/definitions/bsky-get-trending.tool.d.ts.map +1 -0
  34. package/dist/mcp-server/tools/definitions/bsky-get-trending.tool.js +81 -0
  35. package/dist/mcp-server/tools/definitions/bsky-get-trending.tool.js.map +1 -0
  36. package/dist/mcp-server/tools/definitions/bsky-search-actors.tool.d.ts +27 -0
  37. package/dist/mcp-server/tools/definitions/bsky-search-actors.tool.d.ts.map +1 -0
  38. package/dist/mcp-server/tools/definitions/bsky-search-actors.tool.js +100 -0
  39. package/dist/mcp-server/tools/definitions/bsky-search-actors.tool.js.map +1 -0
  40. package/dist/mcp-server/tools/definitions/bsky-search-posts.tool.d.ts +47 -0
  41. package/dist/mcp-server/tools/definitions/bsky-search-posts.tool.d.ts.map +1 -0
  42. package/dist/mcp-server/tools/definitions/bsky-search-posts.tool.js +233 -0
  43. package/dist/mcp-server/tools/definitions/bsky-search-posts.tool.js.map +1 -0
  44. package/dist/services/bluesky/bluesky-service.d.ts +68 -0
  45. package/dist/services/bluesky/bluesky-service.d.ts.map +1 -0
  46. package/dist/services/bluesky/bluesky-service.js +287 -0
  47. package/dist/services/bluesky/bluesky-service.js.map +1 -0
  48. package/dist/services/bluesky/types.d.ts +126 -0
  49. package/dist/services/bluesky/types.d.ts.map +1 -0
  50. package/dist/services/bluesky/types.js +6 -0
  51. package/dist/services/bluesky/types.js.map +1 -0
  52. package/package.json +102 -0
  53. package/server.json +99 -0
package/README.md ADDED
@@ -0,0 +1,307 @@
1
+ <div align="center">
2
+ <h1>@cyanheads/bluesky-mcp-server</h1>
3
+ <p><b>Search posts, profiles, feeds, threads, and trending topics on Bluesky via MCP. STDIO or Streamable HTTP.</b>
4
+ <div>7 Tools • 1 Resource</div>
5
+ </p>
6
+ </div>
7
+
8
+ <div align="center">
9
+
10
+ [![Version](https://img.shields.io/badge/Version-0.1.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/bluesky-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/bluesky-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/bluesky-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.11-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
+
12
+ </div>
13
+
14
+ <div align="center">
15
+
16
+ [![Install in Claude Desktop](https://img.shields.io/badge/Install_in-Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white)](https://github.com/cyanheads/bluesky-mcp-server/releases/latest/download/bluesky-mcp-server.mcpb) [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=bluesky-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvYmx1ZXNreS1tY3Atc2VydmVyIl19) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22bluesky-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40cyanheads%2Fbluesky-mcp-server%22%5D%7D)
17
+
18
+ [![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-67E8F9?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
19
+
20
+ </div>
21
+
22
+ ---
23
+
24
+ ## Tools
25
+
26
+ Seven tools for read-only access to the public Bluesky/AT Protocol AppView — no authentication required:
27
+
28
+ | Tool | Description |
29
+ |:-----|:------------|
30
+ | `bsky_search_posts` | Full-text search across public Bluesky posts, with author, language, tag, domain, date, and sort filters |
31
+ | `bsky_get_profile` | Fetch a Bluesky actor's public profile by handle or DID — the handle↔DID resolver |
32
+ | `bsky_get_author_feed` | A user's recent posts ordered newest-first, filterable by post type |
33
+ | `bsky_get_post_thread` | Fetch the full conversation for a post by AT-URI — parent chain upward and reply tree downward |
34
+ | `bsky_search_actors` | Find Bluesky accounts by name or handle fragment |
35
+ | `bsky_get_follows` | Paginated social graph edges — who a user follows or who follows them |
36
+ | `bsky_get_trending` | Real-time trending topics on Bluesky with post count, category, and status |
37
+
38
+ ### `bsky_search_posts`
39
+
40
+ Full-text search across public Bluesky posts.
41
+
42
+ - Filters: author handle, language (BCP-47), hashtag, domain, date range (`since`/`until`), and sort order (`top` or `latest`)
43
+ - Returns posts with text, author, engagement counts (likes/reposts/replies/quotes), embeds, AT-URIs, and timestamps
44
+ - `hitsTotal` when available — total matching posts, not just the current page
45
+ - Pagination via opaque cursor; up to 100 results per call
46
+ - Embeds normalized into a flat union: `images`, `external` (link cards), `record` (quoted posts), `video`, `unknown`
47
+ - Moderation labels surfaced as-is — not filtered
48
+
49
+ ---
50
+
51
+ ### `bsky_get_profile`
52
+
53
+ Fetch a Bluesky actor's public profile by handle or DID.
54
+
55
+ - Returns displayName, handle, DID, description, follower/following/post counts, avatar URL, moderation labels, and pinned post AT-URI
56
+ - The resolution step for handle↔DID — use before tools that require a DID or AT-URI when you only have a handle
57
+
58
+ ---
59
+
60
+ ### `bsky_get_author_feed`
61
+
62
+ A user's recent posts ordered newest-first.
63
+
64
+ - Filter by post type: `posts_with_replies`, `posts_no_replies`, `posts_with_media`, or `posts_and_author_threads`
65
+ - Returns posts with full text, engagement counts, embeds, and AT-URIs for thread drilling
66
+ - Pagination via cursor
67
+
68
+ ---
69
+
70
+ ### `bsky_get_post_thread`
71
+
72
+ Fetch the full conversation for a post by AT-URI.
73
+
74
+ - Returns the root post, parent chain (upward), and nested reply tree (downward)
75
+ - Configurable `depth` (reply tree depth, default 6) and `parent_height` (parent chain height, default 80)
76
+ - Truncated subtrees surface `truncated: true`; deleted posts surface as `not_found`
77
+ - AT-URIs come from `bsky_search_posts` or `bsky_get_author_feed`
78
+
79
+ ---
80
+
81
+ ### `bsky_get_follows`
82
+
83
+ Fetch social graph edges for an account.
84
+
85
+ - `direction`: `followers` (who follows the actor) or `following` (who the actor follows)
86
+ - Returns paginated profiles with handle, DID, displayName, description, and follower count
87
+ - Includes the subject's profile summary at the top level
88
+
89
+ ---
90
+
91
+ ### `bsky_get_trending`
92
+
93
+ Fetch real-time trending topics on Bluesky.
94
+
95
+ - Returns topics with display name, post count, category (politics, sports, pop-culture, etc.), status (hot/rising), and start time
96
+ - No cursor — returns the current snapshot up to `limit`
97
+ - Uses `app.bsky.unspecced.getTrends` — Bluesky may change this endpoint without notice
98
+
99
+ ## Resource
100
+
101
+ | Type | Name | Description |
102
+ |:-----|:-----|:------------|
103
+ | Resource | `bsky://profile/{actor}` | A Bluesky actor's public profile, addressable by handle or DID |
104
+
105
+ All resource data is also reachable via tools. Use `bsky_get_profile` for programmatic access or `bsky://profile/{actor}` to inject profile context directly.
106
+
107
+ ## Features
108
+
109
+ Built on [`@cyanheads/mcp-ts-core`](https://www.npmjs.com/package/@cyanheads/mcp-ts-core):
110
+
111
+ - Declarative tool and resource definitions — single file per primitive, framework handles registration and validation
112
+ - Unified error handling — handlers throw, framework catches, classifies, and formats
113
+ - Pluggable auth: `none`, `jwt`, `oauth`
114
+ - Swappable storage backends: `in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`
115
+ - Structured logging with optional OpenTelemetry tracing
116
+ - STDIO and Streamable HTTP transports
117
+
118
+ Bluesky-specific:
119
+
120
+ - No authentication required — all seven tools operate against `api.bsky.app` without credentials
121
+ - Single `BlueskyService` wrapping the AT Protocol public AppView with retry (3 attempts, 500ms base), 15s timeout, and a versioned `User-Agent`
122
+ - Embed normalization — raw nested AT Protocol embed objects flattened to a clean `type`-discriminated union
123
+ - Moderation labels surfaced verbatim — the agent and its human decide what to do
124
+ - AT Protocol identifier types (handle, DID, AT-URI) explained at first encounter in every tool description
125
+
126
+ Agent-friendly output:
127
+
128
+ - AT-URIs on every post and resource — chain `bsky_search_posts` → `bsky_get_post_thread` without extra steps
129
+ - Discriminated embed union (`type: "images" | "external" | "record" | "video" | "unknown"`) — branch on data, not `$type` strings
130
+ - `hitsTotal` on search results — communicate result scale to users without extra round trips
131
+ - Truncation signals (`truncated: true`) on thread nodes — agents know where the tree ends and why
132
+
133
+ ## Getting started
134
+
135
+ Add the following to your MCP client configuration file. No API key required.
136
+
137
+ ```json
138
+ {
139
+ "mcpServers": {
140
+ "bluesky-mcp-server": {
141
+ "type": "stdio",
142
+ "command": "bunx",
143
+ "args": ["@cyanheads/bluesky-mcp-server@latest"],
144
+ "env": {
145
+ "MCP_TRANSPORT_TYPE": "stdio",
146
+ "MCP_LOG_LEVEL": "info"
147
+ }
148
+ }
149
+ }
150
+ }
151
+ ```
152
+
153
+ Or with npx (no Bun required):
154
+
155
+ ```json
156
+ {
157
+ "mcpServers": {
158
+ "bluesky-mcp-server": {
159
+ "type": "stdio",
160
+ "command": "npx",
161
+ "args": ["-y", "@cyanheads/bluesky-mcp-server@latest"],
162
+ "env": {
163
+ "MCP_TRANSPORT_TYPE": "stdio",
164
+ "MCP_LOG_LEVEL": "info"
165
+ }
166
+ }
167
+ }
168
+ }
169
+ ```
170
+
171
+ Or with Docker:
172
+
173
+ ```json
174
+ {
175
+ "mcpServers": {
176
+ "bluesky-mcp-server": {
177
+ "type": "stdio",
178
+ "command": "docker",
179
+ "args": [
180
+ "run", "-i", "--rm",
181
+ "-e", "MCP_TRANSPORT_TYPE=stdio",
182
+ "ghcr.io/cyanheads/bluesky-mcp-server:latest"
183
+ ]
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ For Streamable HTTP, set the transport and start the server:
190
+
191
+ ```sh
192
+ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
193
+ # Server listens at http://localhost:3010/mcp
194
+ ```
195
+
196
+ ### Prerequisites
197
+
198
+ - [Bun v1.3.0](https://bun.sh/) or higher (or Node.js v24+).
199
+ - No API key or account required — all tools call `api.bsky.app` without credentials.
200
+
201
+ ### Installation
202
+
203
+ 1. **Clone the repository:**
204
+
205
+ ```sh
206
+ git clone https://github.com/cyanheads/bluesky-mcp-server.git
207
+ ```
208
+
209
+ 2. **Navigate into the directory:**
210
+
211
+ ```sh
212
+ cd bluesky-mcp-server
213
+ ```
214
+
215
+ 3. **Install dependencies:**
216
+
217
+ ```sh
218
+ bun install
219
+ ```
220
+
221
+ 4. **Configure environment (optional):**
222
+
223
+ ```sh
224
+ cp .env.example .env
225
+ # edit .env to override any framework defaults
226
+ ```
227
+
228
+ ## Configuration
229
+
230
+ This server requires no API keys. All framework configuration is optional.
231
+
232
+ | Variable | Description | Default |
233
+ |:---------|:------------|:--------|
234
+ | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http` | `stdio` |
235
+ | `MCP_HTTP_PORT` | Port for HTTP server | `3010` |
236
+ | `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth` | `none` |
237
+ | `MCP_LOG_LEVEL` | Log level (RFC 5424) | `info` |
238
+ | `LOGS_DIR` | Directory for log files (Node.js only) | `<project-root>/logs` |
239
+ | `STORAGE_PROVIDER_TYPE` | Storage backend | `in-memory` |
240
+ | `OTEL_ENABLED` | Enable [OpenTelemetry instrumentation](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry) | `false` |
241
+
242
+ See [`.env.example`](./.env.example) for the full list of optional overrides.
243
+
244
+ ## Running the server
245
+
246
+ ### Local development
247
+
248
+ - **Build and run:**
249
+
250
+ ```sh
251
+ # One-time build
252
+ bun run rebuild
253
+
254
+ # Run the built server
255
+ bun run start:stdio
256
+ # or
257
+ bun run start:http
258
+ ```
259
+
260
+ - **Run checks and tests:**
261
+
262
+ ```sh
263
+ bun run devcheck # Lint, format, typecheck, security
264
+ bun run test # Vitest test suite
265
+ bun run lint:mcp # Validate MCP definitions against spec
266
+ ```
267
+
268
+ ### Docker
269
+
270
+ ```sh
271
+ docker build -t bluesky-mcp-server .
272
+ docker run --rm -p 3010:3010 bluesky-mcp-server
273
+ ```
274
+
275
+ The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `/var/log/bluesky-mcp-server`. OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.
276
+
277
+ ## Project structure
278
+
279
+ | Directory | Purpose |
280
+ |:----------|:--------|
281
+ | `src/index.ts` | `createApp()` entry point — registers tools, resource, and inits service. |
282
+ | `src/services/bluesky` | AT Protocol AppView HTTP client with retry, timeout, and `User-Agent`. |
283
+ | `src/mcp-server/tools` | Tool definitions (`*.tool.ts`) — seven read-only Bluesky tools. |
284
+ | `src/mcp-server/resources` | Resource definitions (`*.resource.ts`) — `bsky://profile/{actor}`. |
285
+ | `tests/` | Unit and integration tests mirroring `src/`. |
286
+
287
+ ## Development guide
288
+
289
+ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:
290
+
291
+ - Handlers throw, framework catches — no `try/catch` in tool logic
292
+ - Use `ctx.log` for request-scoped logging, `ctx.state` for tenant-scoped storage
293
+ - Register new tools and resources via the arrays in `src/index.ts`
294
+ - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
295
+
296
+ ## Contributing
297
+
298
+ Issues and pull requests are welcome. Run checks and tests before submitting:
299
+
300
+ ```sh
301
+ bun run devcheck
302
+ bun run test
303
+ ```
304
+
305
+ ## License
306
+
307
+ Apache-2.0 — see [LICENSE](LICENSE) for details.
@@ -0,0 +1,24 @@
1
+ ---
2
+ summary: "Initial public release — 7 tools and 1 resource over the Bluesky/AT Protocol public AppView API; input-length bounds on all free-text fields"
3
+ breaking: false
4
+ security: true
5
+ ---
6
+
7
+ # 0.1.1 — 2026-06-05
8
+
9
+ Initial public release of `@cyanheads/bluesky-mcp-server`. Provides read-only MCP access to the [Bluesky AT Protocol AppView API](https://docs.bsky.app/docs/category/http-reference) — no authentication required.
10
+
11
+ ## Added
12
+
13
+ - **`bsky_search_posts`** — full-text post search with author, language, tag, domain, `since`/`until`, and sort filters; returns posts with text, engagement counts, embeds, AT-URIs, and timestamps
14
+ - **`bsky_get_profile`** — actor profile lookup by handle or DID; returns displayName, handle, DID, description, follower/following/post counts, avatar URL, moderation labels, and pinned post AT-URI
15
+ - **`bsky_get_author_feed`** — user's recent posts newest-first, filterable by `posts_with_replies`, `posts_no_replies`, `posts_with_media`, or `posts_and_author_threads`; returns posts with full text, engagement counts, embeds, and AT-URIs
16
+ - **`bsky_get_post_thread`** — full conversation for a post by AT-URI; returns root post, parent chain (upward), and nested reply tree (downward); configurable `depth` and `parent_height`
17
+ - **`bsky_search_actors`** — actor discovery by name or handle fragment; returns handle, DID, and display name
18
+ - **`bsky_get_follows`** — paginated social graph edges; `direction` selects followers or following
19
+ - **`bsky_get_trending`** — real-time trending topics with post count, category, and status
20
+ - **`bsky://profile/{actor}`** resource — actor profile addressable by handle or DID via `bsky_profile`
21
+
22
+ ## Security
23
+
24
+ - **Input-length bounds** added to all free-text Zod fields across tools and the resource — actor handles capped at `.max(253)` (max DNS label length), search queries at `.max(500)`, pagination cursors at `.max(2048)`, AT-URIs at `.max(2048)`, date strings at `.max(32)`, language codes at `.max(10)`, and hashtags at `.max(100)` — prevents unbounded string forwarding to the upstream API
@@ -0,0 +1,127 @@
1
+ ---
2
+ # FORMAT REFERENCE — do not edit. Copy this file to
3
+ # `changelog/<major.minor>.x/<version>.md` (e.g. `changelog/0.8.x/0.8.6.md`)
4
+ # to author a new release. Set that file's H1 to `# <version> — YYYY-MM-DD`
5
+ # with a concrete date.
6
+
7
+ # Required. One-line GitHub Release-style headline. 350 character cap.
8
+ # Default short and scannable. Don't pad, don't stitch unrelated changes with
9
+ # semicolons — pick the headline. Quotes required: unquoted YAML treats `: `
10
+ # inside the value as a key separator and fails GitHub's strict parser.
11
+ summary: ""
12
+
13
+ # Set `true` when consumers must change code to upgrade: API removals,
14
+ # signature changes, config renames, behavior changes that break existing
15
+ # usage. Flagged as `Breaking` in the rollup.
16
+ breaking: false
17
+
18
+ # Set `true` if this release contains any security fix. Pairs with the
19
+ # `## Security` section below. Flagged as `Security` in the rollup so
20
+ # users can triage upgrade urgency at a glance.
21
+ security: false
22
+
23
+ # Optional free-form notes for maintenance agents processing this release.
24
+ # Not rendered in CHANGELOG — consumed by agents running `maintenance` on
25
+ # downstream servers. Use for adoption instructions that don't fit the
26
+ # human-facing sections: new files to create, fields to populate, one-time
27
+ # migration steps. Omit the field entirely when there's nothing to say.
28
+ # agent-notes: |
29
+ # <instructions for downstream maintenance agents>
30
+ ---
31
+
32
+ # <version> — YYYY-MM-DD
33
+
34
+ <!--
35
+ AUTHORING GUIDE — applies to the new per-version file you create from this
36
+ template.
37
+
38
+ Audience: someone scanning release notes to decide what affects them. Lead
39
+ each bullet with the symbol or concept name in **bold** so they can skip
40
+ what's irrelevant and zoom in on what's not.
41
+
42
+ Tone: terse, fact-dense, not verbose. Default to one sentence per bullet —
43
+ name the symbol, state what changed, stop. Use a second sentence only when
44
+ it carries weight. If a bullet feels long, it is.
45
+
46
+ Cut: mechanism walkthroughs (those belong in JSDoc, CLAUDE.md/AGENTS.md, or the
47
+ relevant skill), ceremonial framings ("This release introduces…",
48
+ backwards-compat paragraphs), file-by-file test enumerations, internal
49
+ implementation notes. Prefer code/symbol names over English re-explanations.
50
+
51
+ Narrative intro: skip by default. Add one short sentence only when the
52
+ release theme genuinely needs framing the bullets can't carry.
53
+
54
+ Sections: Keep a Changelog order — Added, Changed, Deprecated, Removed,
55
+ Fixed, Security. Include only sections with entries; delete the rest
56
+ (including the commented-out scaffolding below). Don't ship empty headers.
57
+
58
+ Include: every distinct fact a reader needs to adopt or audit the release —
59
+ new exports, signatures, lint rule IDs, env vars, breaking changes, version
60
+ bumps on shipped skills. Nothing more.
61
+
62
+ Links: link issues, PRs, docs, or skills where they help a reader jump to
63
+ context. Once per item per entry — don't re-link the same issue in summary,
64
+ narrative, and bullet. Skip links for inline symbol names; code spans speak
65
+ for themselves.
66
+
67
+ Issue/PR URLs: use full URLs. GitHub's bare `#NN` auto-link only resolves
68
+ inside its own UI, not in npm reads or local editors.
69
+
70
+ [#38](https://github.com/cyanheads/mcp-ts-core/issues/38) ← issue
71
+ [#42](https://github.com/cyanheads/mcp-ts-core/pull/42) ← PR
72
+
73
+ Verify numbers exist before linking (`gh issue view NN`, `gh pr view NN`).
74
+ Never speculate on a future number — `#42` for an upcoming PR silently
75
+ resolves to whatever real item already owns 42, and timeline previews pull
76
+ in that unrelated item's metadata.
77
+
78
+ TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
79
+ via `gh release create --notes-from-tag`. The tag is a derivative of this
80
+ changelog entry — a condensed, scannable version, not a copy. Format:
81
+
82
+ <theme — omit version number, GitHub prepends it>
83
+ ← blank line
84
+ <1-2 sentence context: what this release does>
85
+ ← blank line
86
+ Dependency bumps: ← section header
87
+ ← blank line
88
+ - `@cyanheads/mcp-ts-core` ^0.9.1 → ^0.9.6 ← bullet
89
+ ← blank line
90
+ Changed: ← only sections with entries
91
+ ← blank line
92
+ - `format()` output includes `query` in text mode
93
+ ← blank line
94
+ Added:
95
+ ← blank line
96
+ - `manifest.json` scaffolded for MCPB bundle support
97
+ - Install badges (Claude Desktop, Cursor, VS Code)
98
+ ← blank line
99
+ <N> tests pass; `bun run devcheck` clean. ← footer
100
+
101
+ Never a flat comma-separated string. Always structured markdown with
102
+ sections. The tag must scan well as a rendered GitHub Release page.
103
+ -->
104
+
105
+ ## Added
106
+
107
+ -
108
+
109
+ ## Changed
110
+
111
+ -
112
+
113
+ <!-- ## Deprecated
114
+
115
+ - -->
116
+
117
+ <!-- ## Removed
118
+
119
+ - -->
120
+
121
+ ## Fixed
122
+
123
+ -
124
+
125
+ <!-- ## Security
126
+
127
+ - -->
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview bluesky-mcp-server MCP server entry point.
4
+ * @module index
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG"}
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview bluesky-mcp-server MCP server entry point.
4
+ * @module index
5
+ */
6
+ import { createApp } from '@cyanheads/mcp-ts-core';
7
+ import { bskyProfileResource } from './mcp-server/resources/definitions/bsky-profile.resource.js';
8
+ import { bskyGetAuthorFeed } from './mcp-server/tools/definitions/bsky-get-author-feed.tool.js';
9
+ import { bskyGetFollows } from './mcp-server/tools/definitions/bsky-get-follows.tool.js';
10
+ import { bskyGetPostThread } from './mcp-server/tools/definitions/bsky-get-post-thread.tool.js';
11
+ import { bskyGetProfile } from './mcp-server/tools/definitions/bsky-get-profile.tool.js';
12
+ import { bskyGetTrending } from './mcp-server/tools/definitions/bsky-get-trending.tool.js';
13
+ import { bskySearchActors } from './mcp-server/tools/definitions/bsky-search-actors.tool.js';
14
+ import { bskySearchPosts } from './mcp-server/tools/definitions/bsky-search-posts.tool.js';
15
+ import { initBlueskyService } from './services/bluesky/bluesky-service.js';
16
+ await createApp({
17
+ tools: [
18
+ bskyGetProfile,
19
+ bskySearchActors,
20
+ bskyGetTrending,
21
+ bskyGetAuthorFeed,
22
+ bskySearchPosts,
23
+ bskyGetPostThread,
24
+ bskyGetFollows,
25
+ ],
26
+ resources: [bskyProfileResource],
27
+ prompts: [],
28
+ instructions: 'Bluesky MCP Server — read-only access to the public AT Protocol AppView.\n' +
29
+ 'No authentication required. All tools call https://api.bsky.app without credentials.\n\n' +
30
+ 'Key identifier types:\n' +
31
+ '- Handle: human-readable username, e.g. "alice.bsky.social"\n' +
32
+ '- DID: permanent identity key, e.g. "did:plc:z72i7hdynmk6r22z27h6tvur"\n' +
33
+ '- AT-URI: post address, e.g. "at://did:plc:.../app.bsky.feed.post/rkey"\n\n' +
34
+ 'Typical workflows:\n' +
35
+ '1. bsky_search_posts — find recent posts on any topic\n' +
36
+ '2. bsky_get_post_thread — read a full conversation (AT-URI from search results)\n' +
37
+ '3. bsky_get_profile — resolve a handle or look up an account\n' +
38
+ '4. bsky_get_trending — discover what Bluesky is talking about right now',
39
+ setup(_core) {
40
+ initBlueskyService();
41
+ },
42
+ });
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6DAA6D,CAAC;AAClG,OAAO,EAAE,iBAAiB,EAAE,MAAM,6DAA6D,CAAC;AAChG,OAAO,EAAE,cAAc,EAAE,MAAM,yDAAyD,CAAC;AACzF,OAAO,EAAE,iBAAiB,EAAE,MAAM,6DAA6D,CAAC;AAChG,OAAO,EAAE,cAAc,EAAE,MAAM,yDAAyD,CAAC;AACzF,OAAO,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,2DAA2D,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAE3E,MAAM,SAAS,CAAC;IACd,KAAK,EAAE;QACL,cAAc;QACd,gBAAgB;QAChB,eAAe;QACf,iBAAiB;QACjB,eAAe;QACf,iBAAiB;QACjB,cAAc;KACf;IACD,SAAS,EAAE,CAAC,mBAAmB,CAAC;IAChC,OAAO,EAAE,EAAE;IACX,YAAY,EACV,4EAA4E;QAC5E,0FAA0F;QAC1F,yBAAyB;QACzB,+DAA+D;QAC/D,0EAA0E;QAC1E,6EAA6E;QAC7E,sBAAsB;QACtB,yDAAyD;QACzD,mFAAmF;QACnF,gEAAgE;QAChE,yEAAyE;IAC3E,KAAK,CAAC,KAAK;QACT,kBAAkB,EAAE,CAAC;IACvB,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @fileoverview Bluesky actor public profile resource, addressable by handle or DID.
3
+ * @module mcp-server/resources/definitions/bsky-profile
4
+ */
5
+ import { z } from '@cyanheads/mcp-ts-core';
6
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
7
+ export declare const bskyProfileResource: import("@cyanheads/mcp-ts-core").ResourceDefinition<z.ZodObject<{
8
+ actor: z.ZodString;
9
+ }, z.core.$strip>, undefined, readonly [{
10
+ readonly reason: "actor_not_found";
11
+ readonly code: JsonRpcErrorCode.NotFound;
12
+ readonly when: "The handle does not resolve or the profile does not exist.";
13
+ readonly recovery: "Verify the handle spelling or use bsky_search_actors to find the correct handle.";
14
+ }]>;
15
+ //# sourceMappingURL=bsky-profile.resource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bsky-profile.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/bsky-profile.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AAG3E,eAAO,MAAM,mBAAmB;;;;;;;GAuD9B,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @fileoverview Bluesky actor public profile resource, addressable by handle or DID.
3
+ * @module mcp-server/resources/definitions/bsky-profile
4
+ */
5
+ import { resource, 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
+ export const bskyProfileResource = resource('bsky://profile/{actor}', {
9
+ name: 'bsky-profile',
10
+ description: "A Bluesky actor's public profile, addressable by handle or DID. Returns the same data as " +
11
+ 'bsky_get_profile in injectable-context form — displayName, handle, DID, bio, follower/following/post ' +
12
+ 'counts, avatar, moderation labels, and pinned post AT-URI.',
13
+ mimeType: 'application/json',
14
+ params: z.object({
15
+ actor: z
16
+ .string()
17
+ .max(253)
18
+ .describe('Handle (e.g. "alice.bsky.social") or DID (e.g. "did:plc:z72i7hdynmk6r22z27h6tvur") of the actor.'),
19
+ }),
20
+ errors: [
21
+ {
22
+ reason: 'actor_not_found',
23
+ code: JsonRpcErrorCode.NotFound,
24
+ when: 'The handle does not resolve or the profile does not exist.',
25
+ recovery: 'Verify the handle spelling or use bsky_search_actors to find the correct handle.',
26
+ },
27
+ ],
28
+ async handler(params, ctx) {
29
+ ctx.log.debug('Fetching Bluesky profile resource', { actor: params.actor });
30
+ try {
31
+ return await getBlueskyService().getProfile(params.actor, ctx);
32
+ }
33
+ catch (err) {
34
+ if (err instanceof McpError) {
35
+ const body = err.data?.responseBody ?? '';
36
+ if (err.data &&
37
+ (body.includes('not found') || body.includes('Not Found') || body.includes('NotFound'))) {
38
+ throw ctx.fail('actor_not_found', `Actor not found: "${params.actor}"`, ctx.recoveryFor('actor_not_found'));
39
+ }
40
+ }
41
+ throw err;
42
+ }
43
+ },
44
+ list: async () => ({
45
+ resources: [
46
+ {
47
+ uri: 'bsky://profile/bsky.app',
48
+ name: 'Bluesky official profile (bsky.app)',
49
+ mimeType: 'application/json',
50
+ },
51
+ ],
52
+ }),
53
+ });
54
+ //# sourceMappingURL=bsky-profile.resource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bsky-profile.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/bsky-profile.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAE1E,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAQ,CAAC,wBAAwB,EAAE;IACpE,IAAI,EAAE,cAAc;IACpB,WAAW,EACT,2FAA2F;QAC3F,uGAAuG;QACvG,4DAA4D;IAC9D,QAAQ,EAAE,kBAAkB;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,CACP,kGAAkG,CACnG;KACJ,CAAC;IACF,MAAM,EAAE;QACN;YACE,MAAM,EAAE,iBAAiB;YACzB,IAAI,EAAE,gBAAgB,CAAC,QAAQ;YAC/B,IAAI,EAAE,4DAA4D;YAClE,QAAQ,EAAE,kFAAkF;SAC7F;KACF;IAED,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG;QACvB,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,mCAAmC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QAC5E,IAAI,CAAC;YACH,OAAO,MAAM,iBAAiB,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACjE,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,MAAM,CAAC,KAAK,GAAG,EACpC,GAAG,CAAC,WAAW,CAAC,iBAAiB,CAAC,CACnC,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACjB,SAAS,EAAE;YACT;gBACE,GAAG,EAAE,yBAAyB;gBAC9B,IAAI,EAAE,qCAAqC;gBAC3C,QAAQ,EAAE,kBAAkB;aAC7B;SACF;KACF,CAAC;CACH,CAAC,CAAC"}
@@ -0,0 +1,49 @@
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 { z } from '@cyanheads/mcp-ts-core';
6
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
7
+ export declare const bskyGetAuthorFeed: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
8
+ actor: z.ZodString;
9
+ filter: z.ZodDefault<z.ZodEnum<{
10
+ posts_with_replies: "posts_with_replies";
11
+ posts_no_replies: "posts_no_replies";
12
+ posts_with_media: "posts_with_media";
13
+ posts_and_author_threads: "posts_and_author_threads";
14
+ }>>;
15
+ limit: z.ZodDefault<z.ZodNumber>;
16
+ cursor: z.ZodOptional<z.ZodString>;
17
+ }, z.core.$strip>, z.ZodObject<{
18
+ posts: z.ZodArray<z.ZodObject<{
19
+ uri: z.ZodString;
20
+ cid: z.ZodString;
21
+ text: z.ZodString;
22
+ author: z.ZodObject<{
23
+ did: z.ZodString;
24
+ handle: z.ZodString;
25
+ displayName: z.ZodOptional<z.ZodString>;
26
+ avatar: z.ZodOptional<z.ZodString>;
27
+ }, z.core.$strip>;
28
+ replyCount: z.ZodOptional<z.ZodNumber>;
29
+ repostCount: z.ZodOptional<z.ZodNumber>;
30
+ likeCount: z.ZodOptional<z.ZodNumber>;
31
+ quoteCount: z.ZodOptional<z.ZodNumber>;
32
+ indexedAt: z.ZodOptional<z.ZodString>;
33
+ createdAt: z.ZodOptional<z.ZodString>;
34
+ labels: z.ZodOptional<z.ZodArray<z.ZodObject<{
35
+ val: z.ZodString;
36
+ }, z.core.$strip>>>;
37
+ embed: z.ZodOptional<z.ZodObject<{}, z.core.$loose>>;
38
+ replyToUri: z.ZodOptional<z.ZodString>;
39
+ }, z.core.$strip>>;
40
+ cursor: z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strip>, readonly [{
42
+ readonly reason: "actor_not_found";
43
+ readonly code: JsonRpcErrorCode.NotFound;
44
+ readonly when: "The actor handle or DID does not resolve to an existing account.";
45
+ readonly recovery: "Verify the handle or DID, or use bsky_search_actors to find the correct actor.";
46
+ }], {
47
+ readonly totalReturned: z.ZodNumber;
48
+ }>;
49
+ //# sourceMappingURL=bsky-get-author-feed.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bsky-get-author-feed.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/bsky-get-author-feed.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AAiE3E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4J5B,CAAC"}