diff --git a/AGENTS.md b/AGENTS.md
index d9e4b7ca..ac320a4d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -103,6 +103,7 @@ Each example is self-contained and demonstrates specific patterns and capabiliti
- `hackernews_search/` - Hacker News search via the free Algolia API
- `pdf_reader/` - PDF text + metadata extraction (local file or URL, page-range support)
- `searxng_search/` - Privacy-focused search integration
+- `serply_search/` - Google web, news, and scholar search via the Serply API
- `tavily_search/` - Tavily API search tool
- `weather/` - Current conditions and daily/hourly forecast via Open-Meteo (no key)
- `webpage_scraper/` - Web scraping capabilities
diff --git a/README.md b/README.md
index 242f846a..270f5588 100644
--- a/README.md
+++ b/README.md
@@ -352,6 +352,7 @@ Atomic Forge is a collection of tools that can be used with Atomic Agents to ext
- Hacker News Search
- PDF Reader
- SearXNG Search
+- Serply Search
- Tavily Search
- Webpage Scraper
- Weather
diff --git a/atomic-forge/README.md b/atomic-forge/README.md
index bf05ca54..d34c5a0f 100644
--- a/atomic-forge/README.md
+++ b/atomic-forge/README.md
@@ -21,6 +21,7 @@ The Atomic Forge project includes the following tools:
- [Hacker News Search](/atomic-forge/tools/hackernews_search/README.md) — search HN stories, comments, Show HN, Ask HN via the free Algolia API.
- [PDF Reader](/atomic-forge/tools/pdf_reader/README.md) — extract text and metadata from a local or remote PDF, with page-range filtering.
- [SearXNG Search](/atomic-forge/tools/searxng_search/README.md)
+- [Serply Search](/atomic-forge/tools/serply_search/README.md)
- [Tavily Search](/atomic-forge/tools/tavily_search/README.md)
- [Webpage Scraper](/atomic-forge/tools/webpage_scraper/README.md)
- [Weather](/atomic-forge/tools/weather/README.md) — current conditions and forecast via the free Open-Meteo API.
diff --git a/atomic-forge/tools/serply_search/.coveragerc b/atomic-forge/tools/serply_search/.coveragerc
new file mode 100644
index 00000000..c45136ef
--- /dev/null
+++ b/atomic-forge/tools/serply_search/.coveragerc
@@ -0,0 +1,8 @@
+[run]
+source = tool
+omit = */tests/*
+
+[report]
+exclude_lines =
+ if __name__ == "__main__":
+show_missing = True
diff --git a/atomic-forge/tools/serply_search/README.md b/atomic-forge/tools/serply_search/README.md
new file mode 100644
index 00000000..a3679694
--- /dev/null
+++ b/atomic-forge/tools/serply_search/README.md
@@ -0,0 +1,59 @@
+# Serply Search Tool
+
+## Overview
+Searches Google web results, Google News, or Google Scholar through the [Serply](https://serply.io) API and returns each result's title, URL, and snippet, plus the publish date and source for news and the authors and citation count for papers. Requires a Serply API key.
+
+## Prerequisites and Dependencies
+- Python 3.12 or later
+- `atomic-agents`
+- `pydantic`
+- `aiohttp`
+- A Serply API key from [serply.io](https://serply.io)
+
+## Installation
+1. Use the Atomic Assembler CLI: run `atomic` and pick `serply_search`.
+2. Or copy the `tool/` folder directly into your project.
+
+## Configuration
+- `api_key` (str): Serply API key. Falls back to the `SERPLY_API_KEY` environment variable when empty.
+- `base_url` (str): API base URL (default `https://api.serply.io/v1`).
+- `hl` (str): interface language code, e.g. `en` (default `en`).
+- `gl` (str): country code for the search, e.g. `us` (default `us`).
+- `user_agent` (str): user agent sent with each request.
+- `timeout` (float): HTTP timeout in seconds (default 30).
+
+## Input & Output Structure
+
+### Input Schema
+- `queries` (list[str]): search queries to run.
+- `search_type` (str): `search` (Google web), `news` (Google News), or `scholar` (Google Scholar). Default `search`.
+- `max_results_per_query` (int): 1-100 (default 10).
+
+### Output Schema
+A list of `SerplySearchResultItem` items. Each has `query`, `title`, `url`, and optional `description`, `position` (web search), `published` and `source` (news), `authors` and `citations` (scholar).
+
+## Usage
+
+```python
+from tool.serply_search import SerplySearchTool, SerplySearchToolConfig, SerplySearchToolInputSchema
+
+tool = SerplySearchTool(config=SerplySearchToolConfig(api_key="your-serply-api-key"))
+
+output = tool.run(SerplySearchToolInputSchema(
+ queries=["retrieval augmented generation"],
+ search_type="scholar",
+ max_results_per_query=3,
+))
+
+for item in output.results:
+ print(item.title, "-", item.url)
+ print(item.authors, item.citations)
+```
+
+The request and response formats for each endpoint are documented at [serply.io/docs](https://serply.io/docs).
+
+## Contributing
+PRs welcome. See the main repo `CONTRIBUTING.md`.
+
+## License
+Same as the main Atomic Agents project.
diff --git a/atomic-forge/tools/serply_search/pyproject.toml b/atomic-forge/tools/serply_search/pyproject.toml
new file mode 100644
index 00000000..51fd7794
--- /dev/null
+++ b/atomic-forge/tools/serply_search/pyproject.toml
@@ -0,0 +1,34 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.hatch.build.targets.wheel]
+packages = ["tool"]
+
+[project]
+name = "serply-search"
+version = "1.0.0"
+description = "Google web, news, and scholar search tool for Atomic Agents via the Serply API"
+readme = "README.md"
+authors = [
+ { name = "Serply", email = "googio@serply.io" }
+]
+requires-python = ">=3.12"
+dependencies = [
+ "atomic-agents",
+ "pydantic>=2.10.3,<3.0.0",
+ "aiohttp>=3.9.0,<4.0.0",
+]
+
+[dependency-groups]
+dev = [
+ "coverage>=7.6.1,<8.0.0",
+ "pytest>=8.3.3,<9.0.0",
+ "pytest-asyncio>=0.23.5,<1.0.0",
+ "pytest-cov>=5.0.0,<6.0.0",
+ "python-dotenv>=1.0.0,<2.0.0",
+ "rich>=13.7.0,<14.0.0",
+]
+
+[tool.uv.sources]
+atomic-agents = { workspace = true }
diff --git a/atomic-forge/tools/serply_search/requirements.txt b/atomic-forge/tools/serply_search/requirements.txt
new file mode 100644
index 00000000..469c7e98
--- /dev/null
+++ b/atomic-forge/tools/serply_search/requirements.txt
@@ -0,0 +1,3 @@
+atomic-agents>=2.0.0,<3.0.0
+pydantic>=2.10.3,<3.0.0
+aiohttp>=3.9.0,<4.0.0
diff --git a/atomic-forge/tools/serply_search/tests/test_serply_search.py b/atomic-forge/tools/serply_search/tests/test_serply_search.py
new file mode 100644
index 00000000..1a3de146
--- /dev/null
+++ b/atomic-forge/tools/serply_search/tests/test_serply_search.py
@@ -0,0 +1,203 @@
+import os
+import sys
+from types import SimpleNamespace
+from unittest.mock import AsyncMock, MagicMock, patch
+
+import pytest
+
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
+
+from tool.serply_search import ( # noqa: E402
+ SerplySearchResultItem,
+ SerplySearchTool,
+ SerplySearchToolConfig,
+ SerplySearchToolInputSchema,
+ SerplySearchToolOutputSchema,
+)
+
+
+SEARCH_HIT = {
+ "title": "Welcome to Python.org",
+ "description": "Python is a programming language that lets you work quickly.",
+ "position": 1,
+ "realPosition": 1,
+ "result_type": "organic",
+ "link": "https://www.python.org/",
+}
+
+NEWS_HIT = {
+ "title": "Python 3.14 released - example.com",
+ "link": "https://example.com/python-3-14",
+ "published": "Wed, 10 Jun 2026 07:00:00 GMT",
+ "summary": 'Python 3.14 released',
+ "source": {"href": "https://example.com", "title": "example.com"},
+}
+
+SCHOLAR_HIT = {
+ "title": "SciPy 1.0: fundamental algorithms for scientific computing in Python",
+ "link": "https://example.com/scipy-1-0",
+ "id": "W3003257820",
+ "author": {"names": "Pauli Virtanen, Ralf Gommers"},
+ "description": "SciPy is an open-source scientific computing library.",
+ "extras": {"citations": {"count": 12345}},
+}
+
+
+def _mock_session(status: int, payload: dict, reason: str = "OK") -> MagicMock:
+ session = MagicMock()
+ response = SimpleNamespace(status=status, reason=reason, json=AsyncMock(return_value=payload))
+ session.get.return_value.__aenter__.return_value = response
+ return session
+
+
+@pytest.fixture
+def tool():
+ return SerplySearchTool(config=SerplySearchToolConfig(api_key="test-key", hl="de", gl="ch"))
+
+
+def test_to_item_search(tool):
+ item = SerplySearchTool._to_item(SEARCH_HIT, "python")
+ assert isinstance(item, SerplySearchResultItem)
+ assert item.query == "python"
+ assert item.title == "Welcome to Python.org"
+ assert item.url == "https://www.python.org/"
+ assert item.description.startswith("Python is a programming language")
+ assert item.position == 1
+ assert item.published is None
+ assert item.authors is None
+
+
+def test_to_item_news(tool):
+ """News entries carry a summary, a publish date, and a source dict."""
+ item = SerplySearchTool._to_item(NEWS_HIT, "python")
+ assert item.description.startswith(" SerplySearchResultItem:
+ source = hit.get("source")
+ if isinstance(source, dict):
+ source = source.get("title") or source.get("href")
+
+ author = hit.get("author")
+ authors = author.get("names") if isinstance(author, dict) else author
+
+ citations = None
+ extras = hit.get("extras")
+ if isinstance(extras, dict) and isinstance(extras.get("citations"), dict):
+ citations = extras["citations"].get("count")
+
+ return SerplySearchResultItem(
+ query=query,
+ title=hit["title"],
+ url=hit["link"],
+ description=hit.get("description") or hit.get("summary"),
+ position=hit.get("position"),
+ published=hit.get("published"),
+ source=str(source) if source else None,
+ authors=str(authors) if authors else None,
+ citations=citations,
+ )
+
+ async def _fetch(
+ self,
+ session: aiohttp.ClientSession,
+ query: str,
+ search_type: str,
+ max_results: int,
+ ) -> List[SerplySearchResultItem]:
+ params = {"q": query, "num": str(max_results), "hl": self.hl, "gl": self.gl}
+
+ async with session.get(f"{self.base_url}/{search_type}/", params=params) as resp:
+ if resp.status != 200:
+ raise Exception(f"Serply {search_type} failed for '{query}': {resp.status} {resp.reason}")
+ data = await resp.json()
+
+ hits = data.get(self.RESULT_KEYS[search_type], [])
+ items = [self._to_item(hit, query) for hit in hits if hit.get("title") and hit.get("link")]
+ return items[:max_results]
+
+ async def run_async(self, params: SerplySearchToolInputSchema) -> SerplySearchToolOutputSchema:
+ if not self.api_key:
+ raise ValueError(
+ "Serply API key is missing. Set SerplySearchToolConfig.api_key or the SERPLY_API_KEY environment variable."
+ )
+
+ headers = {"X-Api-Key": self.api_key, "User-Agent": self.user_agent, "Accept": "application/json"}
+ timeout = aiohttp.ClientTimeout(total=self.timeout)
+ async with aiohttp.ClientSession(headers=headers, timeout=timeout) as session:
+ tasks = [self._fetch(session, q, params.search_type, params.max_results_per_query) for q in params.queries]
+ grouped = await asyncio.gather(*tasks, return_exceptions=True)
+
+ results: List[SerplySearchResultItem] = []
+ for query, group in zip(params.queries, grouped):
+ if isinstance(group, Exception):
+ logger.warning("Serply query '%s' failed: %s", query, group)
+ continue
+ results.extend(group)
+ return SerplySearchToolOutputSchema(results=results)
+
+ def run(self, params: SerplySearchToolInputSchema) -> SerplySearchToolOutputSchema:
+ with ThreadPoolExecutor() as executor:
+ return executor.submit(asyncio.run, self.run_async(params)).result()
+
+
+#################
+# EXAMPLE USAGE #
+#################
+if __name__ == "__main__": # pragma: no cover
+ from dotenv import load_dotenv
+ from rich.console import Console
+
+ load_dotenv()
+ console = Console()
+ tool = SerplySearchTool(config=SerplySearchToolConfig(api_key=os.getenv("SERPLY_API_KEY", "")))
+
+ for search_type in ("search", "news", "scholar"):
+ output = tool.run(
+ SerplySearchToolInputSchema(
+ queries=["atomic agents framework"],
+ search_type=search_type,
+ max_results_per_query=3,
+ )
+ )
+ console.rule(f"[bold cyan]{search_type}")
+ for item in output.results:
+ console.print(f"[bold]{item.title}[/bold]")
+ console.print(item.url)
+ if item.description:
+ console.print(item.description[:200])
+ if item.published:
+ console.print(f"[bold]Published:[/bold] {item.published} [bold]Source:[/bold] {item.source}")
+ if item.authors:
+ console.print(f"[bold]Authors:[/bold] {item.authors[:120]} [bold]Citations:[/bold] {item.citations}")
+ console.print()
diff --git a/docs/guides/tools.md b/docs/guides/tools.md
index 9b5d9cbe..5d290363 100644
--- a/docs/guides/tools.md
+++ b/docs/guides/tools.md
@@ -153,6 +153,7 @@ The Atomic Forge ships with several pre-built tools:
- **Hacker News Search**: Search HN stories, comments, Show HN, Ask HN, polls (free Algolia API)
- **PDF Reader**: Extract text and metadata from local or remote PDFs, with page-range filtering
- **SearXNG Search**: Search the web using SearXNG
+- **Serply Search**: Google web, news, and scholar search via the Serply API
- **Tavily Search**: AI-powered web search
- **Weather**: Current conditions and daily/hourly forecasts via Open-Meteo (no key required)
- **Webpage Scraper**: Extract content from web pages
diff --git a/uv.lock b/uv.lock
index f17798df..29e756a9 100644
--- a/uv.lock
+++ b/uv.lock
@@ -31,6 +31,7 @@ members = [
"quickstart",
"rag-chatbot",
"searxng-search",
+ "serply-search",
"tavily-search",
"weather-tool",
"web-search-agent",
@@ -3929,6 +3930,43 @@ dev = [
{ name = "rich", specifier = ">=13.7.0,<14.0.0" },
]
+[[package]]
+name = "serply-search"
+version = "1.0.0"
+source = { editable = "atomic-forge/tools/serply_search" }
+dependencies = [
+ { name = "aiohttp" },
+ { name = "atomic-agents" },
+ { name = "pydantic" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "coverage" },
+ { name = "pytest" },
+ { name = "pytest-asyncio" },
+ { name = "pytest-cov" },
+ { name = "python-dotenv" },
+ { name = "rich" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "aiohttp", specifier = ">=3.9.0,<4.0.0" },
+ { name = "atomic-agents", editable = "." },
+ { name = "pydantic", specifier = ">=2.10.3,<3.0.0" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "coverage", specifier = ">=7.6.1,<8.0.0" },
+ { name = "pytest", specifier = ">=8.3.3,<9.0.0" },
+ { name = "pytest-asyncio", specifier = ">=0.23.5,<1.0.0" },
+ { name = "pytest-cov", specifier = ">=5.0.0,<6.0.0" },
+ { name = "python-dotenv", specifier = ">=1.0.0,<2.0.0" },
+ { name = "rich", specifier = ">=13.7.0,<14.0.0" },
+]
+
[[package]]
name = "shellingham"
version = "1.5.4"