atproto-mcp 0.3.0 → 0.4.0

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 (160) hide show
  1. package/README.md +68 -96
  2. package/dist/index.d.ts +6 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +126 -71
  5. package/dist/index.js.map +1 -1
  6. package/dist/tools/implementations/advanced-social-tools.d.ts +326 -47
  7. package/dist/tools/implementations/advanced-social-tools.d.ts.map +1 -1
  8. package/dist/tools/implementations/advanced-social-tools.js +299 -100
  9. package/dist/tools/implementations/advanced-social-tools.js.map +1 -1
  10. package/dist/tools/implementations/analytics-tools.d.ts +67 -166
  11. package/dist/tools/implementations/analytics-tools.d.ts.map +1 -1
  12. package/dist/tools/implementations/analytics-tools.js +129 -488
  13. package/dist/tools/implementations/analytics-tools.js.map +1 -1
  14. package/dist/tools/implementations/analyze-account-tool.d.ts +135 -0
  15. package/dist/tools/implementations/analyze-account-tool.d.ts.map +1 -0
  16. package/dist/tools/implementations/analyze-account-tool.js +757 -0
  17. package/dist/tools/implementations/analyze-account-tool.js.map +1 -0
  18. package/dist/tools/implementations/base-tool.d.ts +57 -0
  19. package/dist/tools/implementations/base-tool.d.ts.map +1 -1
  20. package/dist/tools/implementations/base-tool.js +167 -5
  21. package/dist/tools/implementations/base-tool.js.map +1 -1
  22. package/dist/tools/implementations/batch-operations-tools.d.ts +47 -117
  23. package/dist/tools/implementations/batch-operations-tools.d.ts.map +1 -1
  24. package/dist/tools/implementations/batch-operations-tools.js +123 -222
  25. package/dist/tools/implementations/batch-operations-tools.js.map +1 -1
  26. package/dist/tools/implementations/composite-tools.d.ts +287 -8
  27. package/dist/tools/implementations/composite-tools.d.ts.map +1 -1
  28. package/dist/tools/implementations/composite-tools.js +402 -71
  29. package/dist/tools/implementations/composite-tools.js.map +1 -1
  30. package/dist/tools/implementations/content-discovery-tools.d.ts +207 -75
  31. package/dist/tools/implementations/content-discovery-tools.d.ts.map +1 -1
  32. package/dist/tools/implementations/content-discovery-tools.js +240 -208
  33. package/dist/tools/implementations/content-discovery-tools.js.map +1 -1
  34. package/dist/tools/implementations/content-management-tools.d.ts +71 -6
  35. package/dist/tools/implementations/content-management-tools.d.ts.map +1 -1
  36. package/dist/tools/implementations/content-management-tools.js +126 -50
  37. package/dist/tools/implementations/content-management-tools.js.map +1 -1
  38. package/dist/tools/implementations/create-post-tool.d.ts +111 -9
  39. package/dist/tools/implementations/create-post-tool.d.ts.map +1 -1
  40. package/dist/tools/implementations/create-post-tool.js +175 -82
  41. package/dist/tools/implementations/create-post-tool.js.map +1 -1
  42. package/dist/tools/implementations/create-thread-tool.d.ts +68 -0
  43. package/dist/tools/implementations/create-thread-tool.d.ts.map +1 -1
  44. package/dist/tools/implementations/create-thread-tool.js +104 -10
  45. package/dist/tools/implementations/create-thread-tool.js.map +1 -1
  46. package/dist/tools/implementations/discover-tool.d.ts +111 -0
  47. package/dist/tools/implementations/discover-tool.d.ts.map +1 -0
  48. package/dist/tools/implementations/discover-tool.js +556 -0
  49. package/dist/tools/implementations/discover-tool.js.map +1 -0
  50. package/dist/tools/implementations/follow-user-tool.d.ts +69 -6
  51. package/dist/tools/implementations/follow-user-tool.d.ts.map +1 -1
  52. package/dist/tools/implementations/follow-user-tool.js +102 -61
  53. package/dist/tools/implementations/follow-user-tool.js.map +1 -1
  54. package/dist/tools/implementations/get-author-feed-tool.d.ts +164 -0
  55. package/dist/tools/implementations/get-author-feed-tool.d.ts.map +1 -0
  56. package/dist/tools/implementations/get-author-feed-tool.js +147 -0
  57. package/dist/tools/implementations/get-author-feed-tool.js.map +1 -0
  58. package/dist/tools/implementations/get-user-profile-tool.d.ts +113 -0
  59. package/dist/tools/implementations/get-user-profile-tool.d.ts.map +1 -1
  60. package/dist/tools/implementations/get-user-profile-tool.js +150 -24
  61. package/dist/tools/implementations/get-user-profile-tool.js.map +1 -1
  62. package/dist/tools/implementations/index.d.ts +11 -12
  63. package/dist/tools/implementations/index.d.ts.map +1 -1
  64. package/dist/tools/implementations/index.js +12 -15
  65. package/dist/tools/implementations/index.js.map +1 -1
  66. package/dist/tools/implementations/like-post-tool.d.ts +63 -1
  67. package/dist/tools/implementations/like-post-tool.d.ts.map +1 -1
  68. package/dist/tools/implementations/like-post-tool.js +63 -19
  69. package/dist/tools/implementations/like-post-tool.js.map +1 -1
  70. package/dist/tools/implementations/media-tools.d.ts +184 -225
  71. package/dist/tools/implementations/media-tools.d.ts.map +1 -1
  72. package/dist/tools/implementations/media-tools.js +249 -225
  73. package/dist/tools/implementations/media-tools.js.map +1 -1
  74. package/dist/tools/implementations/moderation-tools.d.ts +324 -3
  75. package/dist/tools/implementations/moderation-tools.d.ts.map +1 -1
  76. package/dist/tools/implementations/moderation-tools.js +372 -27
  77. package/dist/tools/implementations/moderation-tools.js.map +1 -1
  78. package/dist/tools/implementations/reply-to-post-tool.d.ts +44 -2
  79. package/dist/tools/implementations/reply-to-post-tool.d.ts.map +1 -1
  80. package/dist/tools/implementations/reply-to-post-tool.js +63 -41
  81. package/dist/tools/implementations/reply-to-post-tool.js.map +1 -1
  82. package/dist/tools/implementations/repost-tool.d.ts +77 -1
  83. package/dist/tools/implementations/repost-tool.d.ts.map +1 -1
  84. package/dist/tools/implementations/repost-tool.js +149 -21
  85. package/dist/tools/implementations/repost-tool.js.map +1 -1
  86. package/dist/tools/implementations/rich-media-tools.d.ts +53 -84
  87. package/dist/tools/implementations/rich-media-tools.d.ts.map +1 -1
  88. package/dist/tools/implementations/rich-media-tools.js +77 -190
  89. package/dist/tools/implementations/rich-media-tools.js.map +1 -1
  90. package/dist/tools/implementations/search-actors-tool.d.ts +109 -0
  91. package/dist/tools/implementations/search-actors-tool.d.ts.map +1 -0
  92. package/dist/tools/implementations/search-actors-tool.js +110 -0
  93. package/dist/tools/implementations/search-actors-tool.js.map +1 -0
  94. package/dist/tools/implementations/search-posts-tool.d.ts +113 -5
  95. package/dist/tools/implementations/search-posts-tool.d.ts.map +1 -1
  96. package/dist/tools/implementations/search-posts-tool.js +127 -63
  97. package/dist/tools/implementations/search-posts-tool.js.map +1 -1
  98. package/dist/tools/implementations/social-graph-tools.d.ts +227 -48
  99. package/dist/tools/implementations/social-graph-tools.d.ts.map +1 -1
  100. package/dist/tools/implementations/social-graph-tools.js +268 -148
  101. package/dist/tools/implementations/social-graph-tools.js.map +1 -1
  102. package/dist/tools/implementations/timeline-tools.d.ts +91 -4
  103. package/dist/tools/implementations/timeline-tools.d.ts.map +1 -1
  104. package/dist/tools/implementations/timeline-tools.js +81 -56
  105. package/dist/tools/implementations/timeline-tools.js.map +1 -1
  106. package/dist/tools/index.d.ts +13 -0
  107. package/dist/tools/index.d.ts.map +1 -1
  108. package/dist/tools/index.js +8 -27
  109. package/dist/tools/index.js.map +1 -1
  110. package/dist/types/index.d.ts +14 -5
  111. package/dist/types/index.d.ts.map +1 -1
  112. package/dist/types/index.js +0 -9
  113. package/dist/types/index.js.map +1 -1
  114. package/dist/utils/atp-client.d.ts +22 -2
  115. package/dist/utils/atp-client.d.ts.map +1 -1
  116. package/dist/utils/atp-client.js +98 -15
  117. package/dist/utils/atp-client.js.map +1 -1
  118. package/dist/utils/config.d.ts +12 -0
  119. package/dist/utils/config.d.ts.map +1 -1
  120. package/dist/utils/config.js +45 -37
  121. package/dist/utils/config.js.map +1 -1
  122. package/dist/utils/firehose-client.d.ts +19 -1
  123. package/dist/utils/firehose-client.d.ts.map +1 -1
  124. package/dist/utils/firehose-client.js +96 -14
  125. package/dist/utils/firehose-client.js.map +1 -1
  126. package/dist/utils/logger.d.ts +8 -0
  127. package/dist/utils/logger.d.ts.map +1 -1
  128. package/dist/utils/logger.js +12 -1
  129. package/dist/utils/logger.js.map +1 -1
  130. package/dist/utils/oauth-client.d.ts.map +1 -1
  131. package/dist/utils/oauth-client.js +6 -2
  132. package/dist/utils/oauth-client.js.map +1 -1
  133. package/dist/utils/security.d.ts +8 -0
  134. package/dist/utils/security.d.ts.map +1 -1
  135. package/dist/utils/security.js +31 -6
  136. package/dist/utils/security.js.map +1 -1
  137. package/dist/utils/url-safety.d.ts.map +1 -1
  138. package/dist/utils/url-safety.js +37 -0
  139. package/dist/utils/url-safety.js.map +1 -1
  140. package/package.json +6 -7
  141. package/dist/tools/implementations/analyze-engagement-tool.d.ts +0 -105
  142. package/dist/tools/implementations/analyze-engagement-tool.d.ts.map +0 -1
  143. package/dist/tools/implementations/analyze-engagement-tool.js +0 -229
  144. package/dist/tools/implementations/analyze-engagement-tool.js.map +0 -1
  145. package/dist/tools/implementations/discover-trending-tool.d.ts +0 -107
  146. package/dist/tools/implementations/discover-trending-tool.d.ts.map +0 -1
  147. package/dist/tools/implementations/discover-trending-tool.js +0 -284
  148. package/dist/tools/implementations/discover-trending-tool.js.map +0 -1
  149. package/dist/tools/implementations/generate-alt-text-tool.d.ts +0 -77
  150. package/dist/tools/implementations/generate-alt-text-tool.d.ts.map +0 -1
  151. package/dist/tools/implementations/generate-alt-text-tool.js +0 -128
  152. package/dist/tools/implementations/generate-alt-text-tool.js.map +0 -1
  153. package/dist/tools/implementations/oauth-tools.d.ts +0 -108
  154. package/dist/tools/implementations/oauth-tools.d.ts.map +0 -1
  155. package/dist/tools/implementations/oauth-tools.js +0 -202
  156. package/dist/tools/implementations/oauth-tools.js.map +0 -1
  157. package/dist/tools/implementations/streaming-tools.d.ts +0 -221
  158. package/dist/tools/implementations/streaming-tools.d.ts.map +0 -1
  159. package/dist/tools/implementations/streaming-tools.js +0 -445
  160. package/dist/tools/implementations/streaming-tools.js.map +0 -1
package/README.md CHANGED
@@ -35,10 +35,13 @@ direct access to the AT Protocol ecosystem, enabling seamless interaction with
35
35
  Bluesky and other AT Protocol-based social networks.
36
36
 
37
37
  **Supports both authenticated and unauthenticated modes** - Start immediately
38
- with public data access (view profiles, fetch follower/following lists), or add
39
- authentication for full functionality (search, write operations, private data,
40
- feeds).
38
+ with public data access (view profiles, search accounts, fetch
39
+ follower/following lists), or add authentication for full functionality (search,
40
+ write operations, private data, feeds).
41
41
 
42
+ > **Zero-config launch**: `npx atproto-mcp` runs the server in unauthenticated
43
+ > public-data mode — no credentials required.
44
+ >
42
45
  > **Recent additions**: Batch operations for bulk actions, advanced analytics
43
46
  > and insights, intelligent content discovery, and a conversation-context
44
47
  > scratchpad resource.
@@ -91,22 +94,24 @@ server to access AT Protocol functionality.
91
94
 
92
95
  ### Core Features
93
96
 
94
- - **Unauthenticated Mode**: Access public data without any setup - view basic
95
- profiles and manage OAuth flows
96
- - **Optional Authentication**: Enable full functionality with app passwords or
97
- OAuth for write operations, feeds, and private data
97
+ - **Zero-config Unauthenticated Mode**: Run `npx atproto-mcp` to access public
98
+ data without any setup - view profiles, search accounts, and fetch
99
+ follower/following lists
100
+ - **Optional Authentication**: Enable full functionality with app passwords for
101
+ write operations, feeds, and private data
98
102
  - **Complete AT Protocol Integration**: Full implementation using official
99
103
  `@atproto/api`
100
104
  - **MCP Server Compliance**: Built with `@modelcontextprotocol/sdk` following
101
105
  MCP specification
102
106
  - **Type-Safe**: Written in TypeScript with strict type checking
103
- - **Comprehensive Tools**: 60 MCP tools for social networking operations
104
- - **Real-time Support** _(experimental)_: WebSocket firehose scaffolding with
105
- keyword/user buffer scanning — frame decoding is not yet implemented, so no
106
- live events are delivered yet
107
+ - **Comprehensive Tools**: 43 MCP tools for social networking operations
107
108
  - **Rate Limiting**: Built-in respect for AT Protocol rate limits
108
109
  - **Extensible**: Modular architecture for easy customization
109
110
 
111
+ > **Planned**: OAuth login and real-time firehose streaming are on the roadmap
112
+ > but not yet functional. App-password authentication is the supported auth path
113
+ > today.
114
+
110
115
  ## Who Is This For?
111
116
 
112
117
  ### Primary Audience: LLM Clients
@@ -151,14 +156,30 @@ should either:
151
156
 
152
157
  ## Installation
153
158
 
159
+ Run it with no install and no configuration — `npx atproto-mcp` launches the
160
+ server in unauthenticated public-data mode immediately:
161
+
154
162
  ```bash
155
- npm install -g atproto-mcp
163
+ npx atproto-mcp
156
164
  ```
157
165
 
158
- Or use with npx:
166
+ Or install globally:
159
167
 
160
168
  ```bash
161
- npx atproto-mcp
169
+ npm install -g atproto-mcp
170
+ ```
171
+
172
+ ### Claude Desktop
173
+
174
+ Add this to your Claude Desktop MCP configuration to run the server with zero
175
+ config:
176
+
177
+ ```json
178
+ {
179
+ "mcpServers": {
180
+ "atproto": { "command": "npx", "args": ["-y", "atproto-mcp"] }
181
+ }
182
+ }
162
183
  ```
163
184
 
164
185
  ## Quick Start
@@ -192,16 +213,18 @@ npx atproto-mcp
192
213
 
193
214
  - View user profiles (`get_user_profile` - works without auth, provides
194
215
  additional viewer-specific data when authenticated)
195
- - View follower/following lists (`get_followers`, `get_follows` - ENHANCED mode:
196
- work without auth, enrich the underlying API call when authenticated)
197
- - Manage OAuth authentication flows (`start_oauth_flow`,
198
- `handle_oauth_callback`, `refresh_oauth_tokens`, `revoke_oauth_tokens`)
216
+ - Search for accounts by handle or name (`search_actors`)
217
+ - List a user's posts (`get_author_feed`)
218
+ - View follower/following lists (`get_user_connections` with
219
+ `direction: 'followers' | 'follows'` - ENHANCED mode: works without auth,
220
+ enriches the underlying API call when authenticated)
199
221
 
200
222
  **Note:** The following features require authentication:
201
223
 
202
224
  - Searching posts and hashtags (`search_posts`) - **API changed in 2025 to
203
225
  require authentication**
204
- - Browsing feeds and threads (`get_thread`, `get_custom_feed`, `get_timeline`)
226
+ - Browsing feeds and threads (`get_post_context`, `get_custom_feed`,
227
+ `get_timeline`)
205
228
  - All write operations (create, like, repost, follow, etc.)
206
229
  - Resources (timeline, profile, notifications) - these are listed but require
207
230
  authentication to return data (the `conversation-context` scratchpad resource
@@ -247,7 +270,7 @@ credentials.
247
270
 
248
271
  ## Available Tools
249
272
 
250
- The server provides **60 MCP tools** across multiple categories. See the
273
+ The server provides **43 MCP tools** across multiple categories. See the
251
274
  [complete API documentation](https://cameronrye.github.io/atproto-mcp/api/) for
252
275
  detailed information on each tool.
253
276
 
@@ -257,34 +280,20 @@ detailed information on each tool.
257
280
 
258
281
  - `get_user_profile` - Retrieve basic user information (ENHANCED mode: works
259
282
  without auth, provides additional viewer-specific data when authenticated)
260
- - `get_followers` - Get follower lists (ENHANCED mode: works without auth,
261
- enriches the underlying API call when authenticated)
262
- - `get_follows` - Get following lists (ENHANCED mode: works without auth,
283
+ - `get_user_summary` - Get a profile with recent posts and engagement stats in
284
+ one call (ENHANCED mode)
285
+ - `search_actors` - Find accounts by handle or display name (ENHANCED mode)
286
+ - `get_author_feed` - List a specific user's posts (ENHANCED mode)
287
+ - `get_user_connections` - Get follower or following lists via
288
+ `direction: 'followers' | 'follows'` (ENHANCED mode: works without auth,
263
289
  enriches the underlying API call when authenticated)
290
+ - `get_post_context` - Get a post with optional thread, author profile,
291
+ engagement metrics, and media (ENHANCED mode)
264
292
 
265
293
  **Rich Media**
266
294
 
267
- - `generate_alt_text` - Generate descriptive alt text for images (PUBLIC mode:
268
- no auth required; experimental — returns an alt-text writing
269
- template/guidance, does not analyze image pixels)
270
295
  - `analyze_image` - Report blob-declared size and MIME type for an image (PUBLIC
271
296
  mode: no auth required; does not decode pixels, so no dimensions/aspect ratio)
272
- - `extract_media_from_post` - Extract media from posts (ENHANCED mode: works
273
- without auth)
274
-
275
- **OAuth Management** _(experimental — token exchange not implemented)_
276
-
277
- > ⚠️ Only the authorization-URL step is functional. The token-exchange steps
278
- > (`handle_oauth_callback`, `refresh_oauth_tokens`, `revoke_oauth_tokens`) are
279
- > **not implemented** and return an error. For working authentication, use app
280
- > passwords (`ATPROTO_IDENTIFIER` + `ATPROTO_PASSWORD`).
281
-
282
- - `start_oauth_flow` - Generate a PKCE authorization URL (experimental)
283
- - `handle_oauth_callback` - Complete OAuth flow (not implemented — returns
284
- error)
285
- - `refresh_oauth_tokens` - Refresh authentication tokens (not implemented —
286
- returns error)
287
- - `revoke_oauth_tokens` - Revoke OAuth tokens (not implemented — returns error)
288
297
 
289
298
  **Note:** As of 2025, the AT Protocol API has changed to require authentication
290
299
  for most endpoints that were previously public, including `search_posts`.
@@ -293,8 +302,9 @@ for most endpoints that were previously public, including `search_posts`.
293
302
 
294
303
  **Social Operations**
295
304
 
296
- - `create_post` - Create new posts with rich text support
297
- - `create_rich_text_post` - Create posts with advanced formatting
305
+ - `create_post` - Create posts with text, auto-detected or explicit richtext
306
+ facets, replies, image/external embeds, and quote posts
307
+ - `create_thread` - Create multi-post threads in one call
298
308
  - `reply_to_post` - Reply to existing posts with threading
299
309
  - `like_post` / `unlike_post` - Like and unlike posts
300
310
  - `repost` / `unrepost` - Repost content with optional quotes
@@ -304,10 +314,11 @@ for most endpoints that were previously public, including `search_posts`.
304
314
 
305
315
  - `search_posts` - Search for posts and content across the network (⚠️ API
306
316
  changed in 2025 to require auth)
307
- - `get_thread` - View post threads and conversations
308
317
  - `get_custom_feed` - Access custom feeds
309
318
  - `get_timeline` - Retrieve personalized timelines
310
- - `get_notifications` - Access notification feeds
319
+ - `get_notifications` - Access notification feeds (use `countOnly: true` for a
320
+ cheap unread badge count)
321
+ - `mark_notifications_seen` - Mark notifications as seen up to a timestamp
311
322
 
312
323
  **Content Management**
313
324
 
@@ -327,56 +338,25 @@ for most endpoints that were previously public, including `search_posts`.
327
338
  - `mute_user` / `unmute_user` - Mute and unmute users
328
339
  - `block_user` / `unblock_user` - Block and unblock users
329
340
  - `report_content` / `report_user` - Report content and users
330
-
331
- **Real-time Streaming & Intelligence** _(experimental — not yet functional)_
332
-
333
- > ⚠️ Firehose frame (CAR/DAG-CBOR) decoding is **not implemented** yet, so these
334
- > tools currently decode no events: `start_streaming` returns a
335
- > `not_implemented` status, and the buffer-scanning tools always return empty
336
- > results. The tool descriptions and responses disclose this. Tracked for a
337
- > future release.
338
-
339
- - `start_streaming` - Start a real-time event stream (returns `not_implemented`)
340
- - `stop_streaming` - Stop an event stream subscription
341
- - `get_streaming_status` - Check streaming status (reports decoding
342
- availability)
343
- - `get_recent_events` - Retrieve buffered events (empty until decoding lands)
344
- - `monitor_keywords` - Scan the event buffer for keywords (empty until decoding
345
- lands)
346
- - `track_users` - Scan the event buffer for specific users (empty until decoding
347
- lands)
341
+ - `analyze_moderation_status` - Check moderation status of content
348
342
 
349
343
  **Batch Operations**
350
344
 
351
- - `batch_follow` - Follow multiple users at once (up to 25)
352
- - `batch_like` - Like multiple posts at once (up to 25)
353
- - `batch_repost` - Repost multiple posts at once (up to 25)
345
+ - `batch_action` - Apply one action across up to 25 targets in a single call via
346
+ `action: 'follow' | 'like' | 'repost'`
354
347
 
355
348
  **Analytics & Insights**
356
349
 
357
- - `analyze_engagement` - Analyze engagement patterns across posts
358
- - `analyze_network` - Analyze user's network and connections
359
- - `suggest_content_strategy` - Get content strategy recommendations based on
360
- performance
350
+ - `analyze_account` - Analyze a single account along one dimension via
351
+ `dimension: 'engagement' | 'network' | 'strategy'`
361
352
  - `find_influential_users` - Find influential users in a topic area
362
353
 
363
354
  **Content Discovery**
364
355
 
365
- - `discover_trending` - Discover trending topics and posts
356
+ - `discover` - Surface timeline content via `mode: 'trending' | 'recommended'`
366
357
  - `find_similar_users` - Find users similar to a given user
367
- - `recommend_content` - Get personalized content recommendations
368
358
  - `discover_communities` - Discover communities around topics
369
359
 
370
- **Composite Operations**
371
-
372
- - `get_user_summary` - Get complete user profile with stats and analysis
373
- - `get_post_context` - Get post with thread, author, and engagement data
374
- - `create_thread` - Create multi-post threads in one call
375
-
376
- **Enhanced Moderation**
377
-
378
- - `analyze_moderation_status` - Check moderation status of content
379
-
380
360
  ## Documentation
381
361
 
382
362
  Visit our [documentation site](https://cameronrye.github.io/atproto-mcp) for:
@@ -400,17 +380,8 @@ export ATPROTO_PASSWORD="your-app-password"
400
380
  atproto-mcp
401
381
  ```
402
382
 
403
- ### OAuth (experimental not yet functional)
404
-
405
- > ⚠️ OAuth token exchange is not implemented, so this cannot complete a login
406
- > yet. App passwords (above) are the recommended/working method. The variables
407
- > below configure the experimental authorization-URL generator.
408
-
409
- ```bash
410
- export ATPROTO_CLIENT_ID="your-client-id"
411
- export ATPROTO_CLIENT_SECRET="your-client-secret" # optional for public clients
412
- atproto-mcp --auth oauth
413
- ```
383
+ App passwords are the supported authentication path. OAuth login is planned but
384
+ not yet functional.
414
385
 
415
386
  ## Development
416
387
 
@@ -508,8 +479,9 @@ npm run test:integration
508
479
 
509
480
  **What's tested:**
510
481
 
511
- - Public/enhanced tools (`get_user_profile`, `get_followers`, `get_follows`) and
512
- authenticated tools (`search_posts`, `get_thread`, `get_custom_feed`)
482
+ - Public/enhanced tools (`get_user_profile`, `get_user_connections`,
483
+ `get_author_feed`) and authenticated tools (`search_posts`,
484
+ `get_post_context`, `get_custom_feed`)
513
485
  - DID and handle resolution
514
486
  - Pagination support
515
487
  - Error handling
package/dist/index.d.ts CHANGED
@@ -36,6 +36,12 @@ export declare class AtpMcpServer {
36
36
  * Register MCP tools with the server
37
37
  */
38
38
  private registerTools;
39
+ /**
40
+ * Normalize an error thrown inside a resource/prompt handler into an McpError:
41
+ * pass an existing McpError through, otherwise log + sanitize and wrap it as an
42
+ * InternalError. Returned (not thrown) so the caller writes `throw this.…`.
43
+ */
44
+ private toHandlerMcpError;
39
45
  /**
40
46
  * Register MCP resources with the server
41
47
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKnE,OAAO,EAAsB,KAAK,gBAAgB,EAAmB,MAAM,kBAAkB,CAAC;AAC9F,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAKlD,OAAO,EAAE,KAAK,mBAAmB,EAAsB,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAwB,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE5E;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAS;gBAEnB,eAAe,GAAE,OAAO,CAAC,gBAAgB,CAAM;IAsD3D;;;OAGG;IACH,OAAO,CAAC,WAAW;IAwBnB;;OAEG;IACH,OAAO,CAAC,aAAa;IA4JrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA2EzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAiEvB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAWvB;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA6EnC;;OAEG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAUlC;;OAEG;YACW,OAAO;IAqDrB;;OAEG;IACI,SAAS,IAAI;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,eAAe,EAAE,OAAO,CAAC;QACzB,QAAQ,EAAE,iBAAiB,GAAG,cAAc,GAAG,OAAO,CAAC;QACvD,iBAAiB,EAAE,OAAO,CAAC;QAC3B,MAAM,EAAE,gBAAgB,CAAC;KAC1B;IAUD;;OAEG;IACI,YAAY,IAAI,SAAS;IAIhC;;;;;OAKG;IACI,SAAS,IAAI,MAAM;IAI1B;;OAEG;IACI,gBAAgB,IAAI,aAAa;IAIxC;;OAEG;IACI,qBAAqB,IAAI,mBAAmB;IAInD;;OAEG;IACI,kBAAkB,IAAI,eAAe;IAI5C;;OAEG;IACI,gBAAgB,IAAI;QACzB,WAAW,EAAE,mBAAmB,CAAC;QACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,MAAM,EAAE;YACN,SAAS,EAAE,OAAO,CAAC;YACnB,eAAe,EAAE,OAAO,CAAC;YACzB,QAAQ,EAAE,iBAAiB,GAAG,cAAc,GAAG,OAAO,CAAC;YACvD,iBAAiB,EAAE,OAAO,CAAC;YAC3B,MAAM,EAAE,gBAAgB,CAAC;SAC1B,CAAC;KACH;CAOF;AAED;;;;;;;;;;;;GAYG;AACH,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKnE,OAAO,EAAsB,KAAK,gBAAgB,EAAmB,MAAM,kBAAkB,CAAC;AAC9F,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIlD,OAAO,EAAE,KAAK,mBAAmB,EAAsB,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAwB,eAAe,EAAE,MAAM,qBAAqB,CAAC;AA4D5E;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAS;gBAEnB,eAAe,GAAE,OAAO,CAAC,gBAAgB,CAAM;IA0D3D;;;OAGG;IACH,OAAO,CAAC,WAAW;IAwBnB;;OAEG;IACH,OAAO,CAAC,aAAa;IAmJrB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAezB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAgEzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAwDvB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAWvB;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA6EnC;;OAEG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAUlC;;OAEG;YACW,OAAO;IAiDrB;;OAEG;IACI,SAAS,IAAI;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,eAAe,EAAE,OAAO,CAAC;QACzB,QAAQ,EAAE,iBAAiB,GAAG,cAAc,GAAG,OAAO,CAAC;QACvD,iBAAiB,EAAE,OAAO,CAAC;QAC3B,MAAM,EAAE,gBAAgB,CAAC;KAC1B;IAUD;;OAEG;IACI,YAAY,IAAI,SAAS;IAIhC;;;;;OAKG;IACI,SAAS,IAAI,MAAM;IAI1B;;OAEG;IACI,gBAAgB,IAAI,aAAa;IAIxC;;OAEG;IACI,qBAAqB,IAAI,mBAAmB;IAInD;;OAEG;IACI,kBAAkB,IAAI,eAAe;IAI5C;;OAEG;IACI,gBAAgB,IAAI;QACzB,WAAW,EAAE,mBAAmB,CAAC;QACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,MAAM,EAAE;YACN,SAAS,EAAE,OAAO,CAAC;YACnB,eAAe,EAAE,OAAO,CAAC;YACzB,QAAQ,EAAE,iBAAiB,GAAG,cAAc,GAAG,OAAO,CAAC;YACvD,iBAAiB,EAAE,OAAO,CAAC;YAC3B,MAAM,EAAE,gBAAgB,CAAC;SAC1B,CAAC;KACH;CAOF;AAED;;;;;;;;;;;;GAYG;AACH,eAAe,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -16,11 +16,65 @@ import { AtpClient } from './utils/atp-client.js';
16
16
  import { Logger } from './utils/logger.js';
17
17
  import { ConfigManager } from './utils/config.js';
18
18
  import { createTools } from './tools/index.js';
19
- import { StartStreamingTool } from './tools/implementations/streaming-tools.js';
20
19
  import { createResources } from './resources/index.js';
21
20
  import { createPrompts } from './prompts/index.js';
22
21
  import { PerformanceMonitor } from './utils/performance.js';
23
22
  import { SecurityManager } from './utils/security.js';
23
+ /**
24
+ * Pure read tools (no writes to the network). readOnlyHint:true lets clients
25
+ * auto-approve them. Curated explicitly per tool — NOT derived from auth mode,
26
+ * which encodes auth requirement, not destructiveness.
27
+ */
28
+ const READ_ONLY_TOOLS = new Set([
29
+ 'analyze_account',
30
+ 'analyze_image',
31
+ 'analyze_moderation_status',
32
+ 'discover',
33
+ 'discover_communities',
34
+ 'find_influential_users',
35
+ 'find_similar_users',
36
+ 'generate_link_preview',
37
+ 'get_author_feed',
38
+ 'get_custom_feed',
39
+ 'get_list',
40
+ 'get_notifications',
41
+ 'get_post_context',
42
+ 'get_timeline',
43
+ 'get_user_connections',
44
+ 'get_user_profile',
45
+ 'get_user_summary',
46
+ 'search_actors',
47
+ 'search_posts',
48
+ ]);
49
+ /**
50
+ * Tools whose effect is irreversible or removes/limits data (deletes, blocks,
51
+ * mutes, reports, removals, token revocation). destructiveHint:true lets clients
52
+ * surface confirmation UI and withhold auto-approval.
53
+ */
54
+ const DESTRUCTIVE_TOOLS = new Set([
55
+ 'block_user',
56
+ 'delete_post',
57
+ 'mute_user',
58
+ 'remove_from_list',
59
+ 'report_content',
60
+ 'report_user',
61
+ 'unfollow_user',
62
+ 'unlike_post',
63
+ 'unrepost',
64
+ ]);
65
+ /**
66
+ * Build the advertised annotations for a tool. openWorldHint is true for every
67
+ * tool (they all reach a live network). A per-tool `schema.annotations` override
68
+ * wins over these defaults.
69
+ */
70
+ function computeToolAnnotations(method, override) {
71
+ return {
72
+ openWorldHint: true,
73
+ ...(READ_ONLY_TOOLS.has(method) ? { readOnlyHint: true } : {}),
74
+ ...(DESTRUCTIVE_TOOLS.has(method) ? { destructiveHint: true } : {}),
75
+ ...(override ?? {}),
76
+ };
77
+ }
24
78
  /**
25
79
  * Main server class for AT Protocol MCP Server
26
80
  */
@@ -58,7 +112,11 @@ export class AtpMcpServer {
58
112
  this.performanceMonitor = new PerformanceMonitor(this.logger);
59
113
  // Initialize security manager
60
114
  const securityConfig = {
61
- enableInputSanitization: true,
115
+ // The blanket HTML/script InputSanitizer is intentionally NOT applied to
116
+ // tool arguments (it would corrupt legitimate post content). Per-field
117
+ // zod validation and url-safety guards are the real defenses, so this flag
118
+ // honestly reports that the object sanitizer does not run on the hot path.
119
+ enableInputSanitization: false,
62
120
  enableRateLimit: true,
63
121
  enableErrorSanitization: true,
64
122
  maxInputLength: 10000,
@@ -118,6 +176,12 @@ export class AtpMcpServer {
118
176
  inputSchema: tool.schema.params
119
177
  ? this.zodToJsonSchema(tool.schema.params)
120
178
  : { type: 'object', properties: {} },
179
+ // Advertise an output schema when the tool declares one. This is purely
180
+ // descriptive metadata in the tools/list payload; because tools/call
181
+ // responses are built by this custom handler (not the SDK's high-level
182
+ // registerTool), it does not trigger structuredContent validation.
183
+ ...(tool.schema.outputSchema ? { outputSchema: tool.schema.outputSchema } : {}),
184
+ annotations: computeToolAnnotations(tool.schema.method, tool.schema.annotations),
121
185
  })),
122
186
  }));
123
187
  // Build a name -> tool lookup so a SINGLE tools/call handler can dispatch
@@ -144,6 +208,17 @@ export class AtpMcpServer {
144
208
  tool: toolName,
145
209
  });
146
210
  }
211
+ // Build an MCP "tool error" result. Per the MCP spec, errors that occur
212
+ // while a (known) tool runs — including invalid arguments, unavailable
213
+ // tools, and rate limiting — are reported as a result with isError: true,
214
+ // NOT as JSON-RPC protocol errors. This lets the calling model SEE the
215
+ // error text and react (fix arguments, authenticate, back off) instead of
216
+ // receiving an opaque transport failure. Protocol errors are reserved for
217
+ // problems with the request itself (e.g. an unknown tool, handled above).
218
+ const toolError = (message) => ({
219
+ content: [{ type: 'text', text: message }],
220
+ isError: true,
221
+ });
147
222
  // Rate-limit tool invocations to guard against runaway loops / abuse.
148
223
  // Note: tool arguments are intentionally NOT passed through the HTML/script
149
224
  // input sanitizer — that sanitizer strips characters (`<`, `>`, collapses
@@ -151,51 +226,38 @@ export class AtpMcpServer {
151
226
  // data. Per-field validation is handled by each tool's zod schema, and
152
227
  // outbound URLs/paths are guarded at their call sites (see url-safety).
153
228
  if (!this.securityManager.checkRateLimit(`tool:${toolName}`)) {
154
- throw new McpError(ErrorCode.InternalError, `Rate limit exceeded for tool "${toolName}". Please slow down and retry shortly.`, { tool: toolName });
229
+ return toolError(`Rate limit exceeded for tool "${toolName}". Please slow down and retry shortly.`);
230
+ }
231
+ // Surface tool availability (e.g. requires authentication) as a result the
232
+ // model can act on, not a protocol error.
233
+ if ('isAvailable' in tool &&
234
+ typeof tool.isAvailable === 'function' &&
235
+ !tool.isAvailable()) {
236
+ const availabilityMessage = 'getAvailabilityMessage' in tool && typeof tool.getAvailabilityMessage === 'function'
237
+ ? tool.getAvailabilityMessage()
238
+ : 'Tool not available';
239
+ return toolError(`Tool not available: ${availabilityMessage}`);
155
240
  }
156
241
  try {
157
- // Check if tool is available before execution
158
- if ('isAvailable' in tool && typeof tool.isAvailable === 'function') {
159
- if (!tool.isAvailable()) {
160
- const availabilityMessage = 'getAvailabilityMessage' in tool &&
161
- typeof tool.getAvailabilityMessage === 'function'
162
- ? tool.getAvailabilityMessage()
163
- : 'Tool not available';
164
- throw new McpError(ErrorCode.InternalError, `Tool not available: ${availabilityMessage}`, {
165
- tool: toolName,
166
- availability: availabilityMessage,
167
- });
168
- }
169
- }
170
242
  const result = await tool.handler(request.params.arguments || {});
171
- // DESIGN DECISION: Return results as formatted JSON text for LLM consumption
172
- //
173
- // This server intentionally returns all tool results as stringified JSON text
174
- // rather than using MCP's structured content types. This is a deliberate
175
- // architectural choice with the following rationale:
176
- //
177
- // 1. Consistency: All tools return the same format, making it easier for LLMs
178
- // to parse and understand responses without needing to handle multiple
179
- // content type variations.
243
+ // Return BOTH representations:
180
244
  //
181
- // 2. Readability: Pretty-printed JSON (with 2-space indentation) is optimized
182
- // for LLM token processing and human readability during debugging.
245
+ // - content[].text: pretty-printed JSON, optimized for LLM consumption.
246
+ // LLMs parse formatted JSON text effectively and it is universally
247
+ // supported across all MCP clients, so it stays the primary channel.
183
248
  //
184
- // 3. Compatibility: Text content is universally supported across all MCP clients,
185
- // ensuring maximum compatibility without client-specific handling.
249
+ // - structuredContent: the same result as a machine-readable object, for
250
+ // non-LLM consumers and tool-chaining clients that would otherwise have
251
+ // to re-parse the pretty-printed string. SDK structuredContent must be a
252
+ // JSON object, so bare arrays/primitives are wrapped under `result`.
186
253
  //
187
- // 4. Debugging: Formatted JSON makes it easier to debug and inspect responses
188
- // in logs and during development.
189
- //
190
- // 5. LLM Processing: LLMs are highly effective at parsing JSON text and can
191
- // extract structured information from formatted JSON strings.
192
- //
193
- // Alternative Approach: MCP supports structured content types (e.g., JSON objects,
194
- // arrays, etc.) which could be used instead. However, testing has shown that
195
- // stringified JSON provides better results for LLM clients in practice.
196
- //
197
- // If you need structured content types for programmatic processing, consider
198
- // parsing the JSON text in your client application.
254
+ // NOTE: we intentionally do NOT declare per-tool `outputSchema` doing so
255
+ // makes the SDK REQUIRE and validate structuredContent on every call and
256
+ // throw on any non-conforming object. structuredContent here is additive
257
+ // and best-effort.
258
+ const structuredContent = result != null && typeof result === 'object' && !Array.isArray(result)
259
+ ? result
260
+ : { result };
199
261
  return {
200
262
  content: [
201
263
  {
@@ -203,31 +265,39 @@ export class AtpMcpServer {
203
265
  text: JSON.stringify(result, null, 2),
204
266
  },
205
267
  ],
268
+ structuredContent,
206
269
  };
207
270
  }
208
271
  catch (error) {
209
272
  this.logger.error(`Tool ${toolName} execution failed`, error);
210
- // Re-throw protocol errors (unknown/unavailable tool) as-is so the
211
- // client receives the accurate JSON-RPC code.
212
- if (error instanceof McpError) {
213
- throw error;
214
- }
215
- // Invalid input is a client-correctable condition: map it to the
216
- // spec's InvalidParams (-32602) rather than InternalError (-32603).
273
+ // Invalid arguments are safe to surface verbatim so the model can fix them.
217
274
  if (error instanceof ValidationError) {
218
- throw new McpError(ErrorCode.InvalidParams, error.message, { tool: toolName });
275
+ return toolError(`Invalid parameters: ${error.message}`);
219
276
  }
220
277
  // Sanitize internal error details before returning to the client.
221
278
  const sanitized = this.securityManager
222
279
  .getErrorSanitizer()
223
280
  .sanitizeError(error instanceof Error ? error : new Error(String(error)));
224
- throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${sanitized.message}`, {
225
- tool: toolName,
226
- });
281
+ return toolError(`Tool execution failed: ${sanitized.message}`);
227
282
  }
228
283
  });
229
284
  this.logger.info(`Registered ${tools.length} MCP tools`);
230
285
  }
286
+ /**
287
+ * Normalize an error thrown inside a resource/prompt handler into an McpError:
288
+ * pass an existing McpError through, otherwise log + sanitize and wrap it as an
289
+ * InternalError. Returned (not thrown) so the caller writes `throw this.…`.
290
+ */
291
+ toHandlerMcpError(error, label, context) {
292
+ this.logger.error(label, error);
293
+ if (error instanceof McpError) {
294
+ return error;
295
+ }
296
+ const sanitized = this.securityManager
297
+ .getErrorSanitizer()
298
+ .sanitizeError(error instanceof Error ? error : new Error(String(error)));
299
+ return new McpError(ErrorCode.InternalError, `${label}: ${sanitized.message}`, context);
300
+ }
231
301
  /**
232
302
  * Register MCP resources with the server
233
303
  */
@@ -272,14 +342,7 @@ export class AtpMcpServer {
272
342
  };
273
343
  }
274
344
  catch (error) {
275
- this.logger.error(`Resource read failed`, error);
276
- if (error instanceof McpError) {
277
- throw error;
278
- }
279
- const sanitized = this.securityManager
280
- .getErrorSanitizer()
281
- .sanitizeError(error instanceof Error ? error : new Error(String(error)));
282
- throw new McpError(ErrorCode.InternalError, `Resource read failed: ${sanitized.message}`, {
345
+ throw this.toHandlerMcpError(error, 'Resource read failed', {
283
346
  uri: request.params.uri,
284
347
  });
285
348
  }
@@ -322,14 +385,9 @@ export class AtpMcpServer {
322
385
  return { messages };
323
386
  }
324
387
  catch (error) {
325
- this.logger.error(`Prompt generation failed`, error);
326
- if (error instanceof McpError) {
327
- throw error;
328
- }
329
- const sanitized = this.securityManager
330
- .getErrorSanitizer()
331
- .sanitizeError(error instanceof Error ? error : new Error(String(error)));
332
- throw new McpError(ErrorCode.InternalError, `Prompt generation failed: ${sanitized.message}`, { name: request.params.name });
388
+ throw this.toHandlerMcpError(error, 'Prompt generation failed', {
389
+ name: request.params.name,
390
+ });
333
391
  }
334
392
  });
335
393
  this.logger.info(`Registered ${prompts.length} MCP prompts`);
@@ -446,9 +504,6 @@ export class AtpMcpServer {
446
504
  }
447
505
  // Release security manager background timers (rate-limiter cleanup).
448
506
  this.securityManager.destroy();
449
- // Disconnect the shared firehose client (if a streaming tool opened one)
450
- // so its socket and heartbeat timer do not outlive the server.
451
- await StartStreamingTool.shutdown();
452
507
  }
453
508
  catch (error) {
454
509
  errors.push(error instanceof Error ? error : new Error(String(error)));