search for standard sites pub-search.waow.tech
search zig blog atproto
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: rename MCP to pub-search

- rename module: leaflet_mcp -> pub_search
- update FastMCP name: "leaflet" -> "pub-search"
- update prompts/docs to cover all ATProto publishing platforms
- update resource URI: leaflet://stats -> pub-search://stats
- update CLI entrypoint: leaflet-mcp -> pub-search
- update README examples

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

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

zzstoatzz fc902b3e 28f39d3d

+73 -74
+1 -1
README.md
··· 19 19 search is also exposed as an MCP server for AI agents like Claude Code: 20 20 21 21 ```bash 22 - claude mcp add-json leaflet-search '{"type": "http", "url": "https://leaflet-search-by-zzstoatzz.fastmcp.app/mcp"}' 22 + claude mcp add-json pub-search '{"type": "http", "url": "https://pub-search-by-zzstoatzz.fastmcp.app/mcp"}' 23 23 ``` 24 24 25 25 see [mcp/README.md](mcp/README.md) for local setup and usage details.
+3 -3
mcp/README.md
··· 7 7 ### hosted (recommended) 8 8 9 9 ```bash 10 - claude mcp add-json leaflet-search '{"type": "http", "url": "https://leaflet-search-by-zzstoatzz.fastmcp.app/mcp"}' 10 + claude mcp add-json pub-search '{"type": "http", "url": "https://pub-search-by-zzstoatzz.fastmcp.app/mcp"}' 11 11 ``` 12 12 13 13 ### local ··· 15 15 run the MCP server locally with `uvx`: 16 16 17 17 ```bash 18 - uvx --from git+https://github.com/zzstoatzz/leaflet-search#subdirectory=mcp leaflet-mcp 18 + uvx --from git+https://github.com/zzstoatzz/leaflet-search#subdirectory=mcp pub-search 19 19 ``` 20 20 21 21 to add it to claude code as a local stdio server: 22 22 23 23 ```bash 24 - claude mcp add leaflet-search -- uvx --from 'git+https://github.com/zzstoatzz/leaflet-search#subdirectory=mcp' leaflet-mcp 24 + claude mcp add pub-search -- uvx --from 'git+https://github.com/zzstoatzz/leaflet-search#subdirectory=mcp' pub-search 25 25 ``` 26 26 27 27 ## workflow
+5 -5
mcp/pyproject.toml
··· 1 1 [project] 2 - name = "leaflet-mcp" 2 + name = "pub-search" 3 3 dynamic = ["version"] 4 - description = "MCP server for Leaflet - search decentralized publications on ATProto" 4 + description = "MCP server for searching ATProto publishing platforms (Leaflet, pckt, and more)" 5 5 readme = "README.md" 6 6 authors = [{ name = "zzstoatzz", email = "thrast36@gmail.com" }] 7 7 requires-python = ">=3.10" 8 8 license = "MIT" 9 9 10 - keywords = ["leaflet", "mcp", "atproto", "publications", "search", "fastmcp"] 10 + keywords = ["pub-search", "mcp", "atproto", "publications", "search", "fastmcp", "leaflet", "pckt"] 11 11 12 12 classifiers = [ 13 13 "Development Status :: 3 - Alpha", ··· 27 27 ] 28 28 29 29 [project.scripts] 30 - leaflet-mcp = "leaflet_mcp.server:main" 30 + pub-search = "pub_search.server:main" 31 31 32 32 [build-system] 33 33 requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"] 34 34 build-backend = "hatchling.build" 35 35 36 36 [tool.hatch.build.targets.wheel] 37 - packages = ["src/leaflet_mcp"] 37 + packages = ["src/pub_search"] 38 38 39 39 [tool.hatch.version] 40 40 source = "uv-dynamic-versioning"
-5
mcp/src/leaflet_mcp/__init__.py
··· 1 - """Leaflet MCP server - search decentralized publications on ATProto.""" 2 - 3 - from leaflet_mcp.server import main, mcp 4 - 5 - __all__ = ["main", "mcp"]
mcp/src/leaflet_mcp/_types.py mcp/src/pub_search/_types.py
+4 -4
mcp/src/leaflet_mcp/client.py mcp/src/pub_search/client.py
··· 1 - """HTTP client for Leaflet search API.""" 1 + """HTTP client for leaflet-search API.""" 2 2 3 3 import os 4 4 from contextlib import asynccontextmanager ··· 7 7 import httpx 8 8 9 9 # configurable via env var, defaults to production 10 - LEAFLET_API_URL = os.getenv("LEAFLET_API_URL", "https://leaflet-search-backend.fly.dev") 10 + API_URL = os.getenv("LEAFLET_SEARCH_API_URL", "https://leaflet-search-backend.fly.dev") 11 11 12 12 13 13 @asynccontextmanager 14 14 async def get_http_client() -> AsyncIterator[httpx.AsyncClient]: 15 - """Get an async HTTP client for Leaflet API requests.""" 15 + """Get an async HTTP client for API requests.""" 16 16 async with httpx.AsyncClient( 17 - base_url=LEAFLET_API_URL, 17 + base_url=API_URL, 18 18 timeout=30.0, 19 19 headers={"Accept": "application/json"}, 20 20 ) as client:
+15 -16
mcp/src/leaflet_mcp/server.py mcp/src/pub_search/server.py
··· 1 - """Leaflet MCP server implementation using fastmcp.""" 1 + """MCP server for searching ATProto publishing platforms.""" 2 2 3 3 from __future__ import annotations 4 4 ··· 6 6 7 7 from fastmcp import FastMCP 8 8 9 - from leaflet_mcp._types import Document, PopularSearch, SearchResult, Stats, Tag 10 - from leaflet_mcp.client import get_http_client 9 + from pub_search._types import Document, PopularSearch, SearchResult, Stats, Tag 10 + from pub_search.client import get_http_client 11 11 12 - mcp = FastMCP("leaflet") 12 + mcp = FastMCP("pub-search") 13 13 14 14 15 15 # ----------------------------------------------------------------------------- ··· 19 19 20 20 @mcp.prompt("usage_guide") 21 21 def usage_guide() -> str: 22 - """instructions for using leaflet MCP tools.""" 22 + """instructions for using pub-search MCP tools.""" 23 23 return """\ 24 - # Leaflet MCP server usage guide 24 + # pub-search MCP usage guide 25 25 26 - Leaflet is a decentralized publishing platform on ATProto (the protocol behind Bluesky). 27 - This MCP server provides search and discovery tools for Leaflet publications. 26 + search documents across ATProto publishing platforms including Leaflet, pckt, and others. 28 27 29 28 ## core tools 30 29 ··· 53 52 documents are identified by AT-URIs like: 54 53 `at://did:plc:abc123/pub.leaflet.document/xyz789` 55 54 56 - you can also browse documents on the web at leaflet.pub 55 + browse the web UI at pub-search.waow.tech 57 56 """ 58 57 59 58 ··· 61 60 def search_tips() -> str: 62 61 """tips for effective searching.""" 63 62 return """\ 64 - # Leaflet search tips 63 + # search tips 65 64 66 65 ## text search 67 66 - searches both document titles and content ··· 95 94 tag: str | None = None, 96 95 limit: int = 5, 97 96 ) -> list[SearchResult]: 98 - """search leaflet documents and publications. 97 + """search documents and publications. 99 98 100 99 searches the full text of documents (titles and content) and publications. 101 100 results include a snippet showing where the match was found. ··· 231 230 232 231 @mcp.tool 233 232 async def get_stats() -> Stats: 234 - """get leaflet index statistics. 233 + """get index statistics. 235 234 236 235 returns: 237 236 document and publication counts ··· 246 245 async def get_popular(limit: int = 5) -> list[PopularSearch]: 247 246 """get popular search queries. 248 247 249 - see what others are searching for on leaflet. 248 + see what others are searching for. 250 249 can inspire new research directions. 251 250 252 251 args: ··· 268 267 # ----------------------------------------------------------------------------- 269 268 270 269 271 - @mcp.resource("leaflet://stats") 270 + @mcp.resource("pub-search://stats") 272 271 async def stats_resource() -> str: 273 - """current leaflet index statistics.""" 272 + """current index statistics.""" 274 273 stats = await get_stats() 275 - return f"Leaflet index: {stats.documents} documents, {stats.publications} publications" 274 + return f"pub search index: {stats.documents} documents, {stats.publications} publications" 276 275 277 276 278 277 # -----------------------------------------------------------------------------
+5
mcp/src/pub_search/__init__.py
··· 1 + """MCP server for searching ATProto publishing platforms.""" 2 + 3 + from pub_search.server import main, mcp 4 + 5 + __all__ = ["main", "mcp"]
+8 -8
mcp/tests/test_mcp.py
··· 1 - """tests for leaflet MCP server.""" 1 + """tests for pub-search MCP server.""" 2 2 3 3 import pytest 4 4 from mcp.types import TextContent ··· 6 6 from fastmcp.client import Client 7 7 from fastmcp.client.transports import FastMCPTransport 8 8 9 - from leaflet_mcp._types import Document, PopularSearch, SearchResult, Stats, Tag 10 - from leaflet_mcp.server import mcp 9 + from pub_search._types import Document, PopularSearch, SearchResult, Stats, Tag 10 + from pub_search.server import mcp 11 11 12 12 13 13 class TestTypes: ··· 93 93 94 94 def test_mcp_server_imports(self): 95 95 """mcp server can be imported without errors.""" 96 - from leaflet_mcp import mcp 96 + from pub_search import mcp 97 97 98 - assert mcp.name == "leaflet" 98 + assert mcp.name == "pub-search" 99 99 100 100 def test_exports(self): 101 101 """all expected exports are available.""" 102 - from leaflet_mcp import main, mcp 102 + from pub_search import main, mcp 103 103 104 104 assert mcp is not None 105 105 assert main is not None ··· 138 138 resources = await client.list_resources() 139 139 140 140 resource_uris = {str(r.uri) for r in resources} 141 - assert "leaflet://stats" in resource_uris 141 + assert "pub-search://stats" in resource_uris 142 142 143 143 async def test_usage_guide_prompt_content(self, client): 144 144 """usage_guide prompt returns helpful content.""" ··· 148 148 assert len(result.messages) > 0 149 149 content = result.messages[0].content 150 150 assert isinstance(content, TextContent) 151 - assert "Leaflet" in content.text 151 + assert "pub-search" in content.text 152 152 assert "search" in content.text 153 153 154 154 async def test_search_tips_prompt_content(self, client):
+32 -32
mcp/uv.lock
··· 691 691 ] 692 692 693 693 [[package]] 694 - name = "leaflet-mcp" 695 - source = { editable = "." } 696 - dependencies = [ 697 - { name = "fastmcp" }, 698 - { name = "httpx" }, 699 - { name = "pdsx" }, 700 - ] 701 - 702 - [package.dev-dependencies] 703 - dev = [ 704 - { name = "pytest" }, 705 - { name = "pytest-asyncio" }, 706 - { name = "pytest-sugar" }, 707 - { name = "ruff" }, 708 - ] 709 - 710 - [package.metadata] 711 - requires-dist = [ 712 - { name = "fastmcp", specifier = ">=2.0" }, 713 - { name = "httpx", specifier = ">=0.28" }, 714 - { name = "pdsx", git = "https://github.com/zzstoatzz/pdsx.git" }, 715 - ] 716 - 717 - [package.metadata.requires-dev] 718 - dev = [ 719 - { name = "pytest", specifier = ">=8.3.0" }, 720 - { name = "pytest-asyncio", specifier = ">=0.25.0" }, 721 - { name = "pytest-sugar" }, 722 - { name = "ruff", specifier = ">=0.12.0" }, 723 - ] 724 - 725 - [[package]] 726 694 name = "libipld" 727 695 version = "3.3.2" 728 696 source = { registry = "https://pypi.org/simple" } ··· 1075 1043 sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } 1076 1044 wheels = [ 1077 1045 { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, 1046 + ] 1047 + 1048 + [[package]] 1049 + name = "pub-search" 1050 + source = { editable = "." } 1051 + dependencies = [ 1052 + { name = "fastmcp" }, 1053 + { name = "httpx" }, 1054 + { name = "pdsx" }, 1055 + ] 1056 + 1057 + [package.dev-dependencies] 1058 + dev = [ 1059 + { name = "pytest" }, 1060 + { name = "pytest-asyncio" }, 1061 + { name = "pytest-sugar" }, 1062 + { name = "ruff" }, 1063 + ] 1064 + 1065 + [package.metadata] 1066 + requires-dist = [ 1067 + { name = "fastmcp", specifier = ">=2.0" }, 1068 + { name = "httpx", specifier = ">=0.28" }, 1069 + { name = "pdsx", git = "https://github.com/zzstoatzz/pdsx.git" }, 1070 + ] 1071 + 1072 + [package.metadata.requires-dev] 1073 + dev = [ 1074 + { name = "pytest", specifier = ">=8.3.0" }, 1075 + { name = "pytest-asyncio", specifier = ">=0.25.0" }, 1076 + { name = "pytest-sugar" }, 1077 + { name = "ruff", specifier = ">=0.12.0" }, 1078 1078 ] 1079 1079 1080 1080 [[package]]