zzstoatzz Claude commited on
Commit
d6bd447
·
1 Parent(s): ce06713

Move search from resource to tool

Browse files

- Search is an action with parameters, not a resource
- Resources are for fetching by URI (profile, timeline, notifications)
- Tools are for actions (post, search, follow, like, repost)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

examples/atproto_mcp/README.md CHANGED
@@ -8,12 +8,12 @@ This example demonstrates a FastMCP server that provides tools and resources for
8
 
9
  - **atproto://profile/status**: Get connection status and profile information
10
  - **atproto://timeline**: Retrieve your timeline feed
11
- - **atproto://search/{query}**: Search for posts by keyword
12
  - **atproto://notifications**: Get recent notifications
13
 
14
  ### Tools (Actions)
15
 
16
  - **post**: Create posts with rich features (text, images, quotes, replies, links, mentions)
 
17
  - **follow**: Follow users by handle
18
  - **like**: Like posts by URI
19
  - **repost**: Share posts by URI
 
8
 
9
  - **atproto://profile/status**: Get connection status and profile information
10
  - **atproto://timeline**: Retrieve your timeline feed
 
11
  - **atproto://notifications**: Get recent notifications
12
 
13
  ### Tools (Actions)
14
 
15
  - **post**: Create posts with rich features (text, images, quotes, replies, links, mentions)
16
+ - **search**: Search for posts by query
17
  - **follow**: Follow users by handle
18
  - **like**: Like posts by URI
19
  - **repost**: Share posts by URI
examples/atproto_mcp/demo.py CHANGED
@@ -55,7 +55,7 @@ async def main(enable_posting: bool = False):
55
 
56
  # 3. Search for posts
57
  print("\n3. Searching for posts about 'Bluesky'...")
58
- result = await client.read_resource("atproto://search/Bluesky")
59
  search: SearchResult = (
60
  json.loads(result[0].text) if result else cast(SearchResult, {})
61
  )
@@ -195,7 +195,6 @@ async def main(enable_posting: bool = False):
195
  print("\n Resources (read-only):")
196
  print(" - atproto://profile/status")
197
  print(" - atproto://timeline")
198
- print(" - atproto://search/{query}")
199
  print(" - atproto://notifications")
200
 
201
  print("\n Tools (actions):")
@@ -206,6 +205,7 @@ async def main(enable_posting: bool = False):
206
  print(" • Replies and threads")
207
  print(" • Quote posts")
208
  print(" • Combinations (quote + image, reply + rich text, etc.)")
 
209
  print(" - follow: Follow users")
210
  print(" - like: Like posts")
211
  print(" - repost: Share posts")
 
55
 
56
  # 3. Search for posts
57
  print("\n3. Searching for posts about 'Bluesky'...")
58
+ result = await client.call_tool("search", {"query": "Bluesky", "limit": 5})
59
  search: SearchResult = (
60
  json.loads(result[0].text) if result else cast(SearchResult, {})
61
  )
 
195
  print("\n Resources (read-only):")
196
  print(" - atproto://profile/status")
197
  print(" - atproto://timeline")
 
198
  print(" - atproto://notifications")
199
 
200
  print("\n Tools (actions):")
 
205
  print(" • Replies and threads")
206
  print(" • Quote posts")
207
  print(" • Combinations (quote + image, reply + rich text, etc.)")
208
+ print(" - search: Search for posts")
209
  print(" - follow: Follow users")
210
  print(" - like: Like posts")
211
  print(" - repost: Share posts")
examples/atproto_mcp/src/atproto_mcp/server.py CHANGED
@@ -41,23 +41,6 @@ def get_timeline() -> TimelineResult:
41
  return _atproto.fetch_timeline(settings.atproto_timeline_default_limit)
42
 
43
 
44
- @atproto_mcp.resource("atproto://search/{query}")
45
- def search_posts(
46
- query: Annotated[str, Field(description="Search query for posts")],
47
- limit: Annotated[
48
- int,
49
- Field(
50
- default=settings.atproto_search_default_limit,
51
- ge=1,
52
- le=100,
53
- description="Number of results to return",
54
- ),
55
- ],
56
- ) -> SearchResult:
57
- """Search for posts containing specific text."""
58
- return _atproto.search_for_posts(query, limit)
59
-
60
-
61
  @atproto_mcp.resource("atproto://notifications")
62
  def get_notifications() -> NotificationsResult:
63
  """Get recent notifications for the authenticated user."""
@@ -132,3 +115,14 @@ def repost(
132
  ) -> RepostResult:
133
  """Repost a post by its AT URI."""
134
  return _atproto.repost_by_uri(uri)
 
 
 
 
 
 
 
 
 
 
 
 
41
  return _atproto.fetch_timeline(settings.atproto_timeline_default_limit)
42
 
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  @atproto_mcp.resource("atproto://notifications")
45
  def get_notifications() -> NotificationsResult:
46
  """Get recent notifications for the authenticated user."""
 
115
  ) -> RepostResult:
116
  """Repost a post by its AT URI."""
117
  return _atproto.repost_by_uri(uri)
118
+
119
+
120
+ @atproto_mcp.tool
121
+ def search(
122
+ query: Annotated[str, Field(description="Search query for posts")],
123
+ limit: Annotated[
124
+ int, Field(ge=1, le=100, description="Number of results to return")
125
+ ] = settings.atproto_search_default_limit,
126
+ ) -> SearchResult:
127
+ """Search for posts containing specific text."""
128
+ return _atproto.search_for_posts(query, limit)