crewai-webzio 0.1.0__tar.gz

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.
@@ -0,0 +1,2 @@
1
+ WEBZ_API_TOKEN=
2
+ WEBZ_MCP_URL=https://news-search-mcp.webz.io/mcp
@@ -0,0 +1,19 @@
1
+ .venv/
2
+ venv/
3
+ .idea/
4
+ __pycache__/
5
+ *.py[cod]
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ .env
13
+ .env.*
14
+ !.env.example
15
+ langchain_tests/
16
+ node_modules/
17
+ *.db
18
+ *.tsbuildinfo
19
+ *.tgz
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Webz.io
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.5
2
+ Name: crewai-webzio
3
+ Version: 0.1.0
4
+ Summary: CrewAI tools for Webz.io News Search - global news in natural language with semantic ranking and rich filters
5
+ Project-URL: Homepage, https://news-search-mcp.webz.io
6
+ Project-URL: Documentation, https://docs.webz.io/docs/webz/news-search-api-mcp
7
+ Project-URL: Repository, https://github.com/Webhose/webz-news-search
8
+ Project-URL: Issues, https://github.com/Webhose/webz-news-search/issues
9
+ Author-email: "Webz.io" <support@webz.io>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: crewai,mcp,news,search,webz,webzio
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: crewai-tools[mcp]>=0.40.0
23
+ Requires-Dist: crewai>=0.80.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # crewai-webzio
29
+
30
+ **Search global news with [Webz.io](https://webz.io) from [CrewAI](https://crewai.com) agents — in natural language, with the most relevant articles first.**
31
+
32
+ [Webz.io News Search](https://docs.webz.io/docs/webz/news-search-api-mcp) covers news and current events from sources worldwide. Ask a question in plain language, narrow results with filters (language, country, date, sentiment, domain, ticker, and more), and get back focused article excerpts with titles, URLs, and metadata.
33
+
34
+ Use this package with CrewAI agents, or construct the tool directly to verify MCP connectivity.
35
+
36
+ ## What you get
37
+
38
+ - **Natural-language search** — no keyword hacking. Example: `"EU AI Act enforcement updates"` or `"How is Tesla stock reacting to earnings?"`
39
+ - **Worldwide coverage** — semantic search over Webz.io's global news index.
40
+ - **Rich filters** — language, country, days, sentiment, domain, ticker, person, organization, topic, and more. See the [MCP tool reference](https://docs.webz.io/docs/webz/news-search-api-mcp#tool-reference).
41
+ - **Live schema** — filters are loaded from the hosted MCP server (`tools/list`). New Webz filters appear automatically, without republishing this package.
42
+ - **CrewAI tool** — pass `WebzioNewsSearchTool(...)` to `Agent(tools=[...])`.
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install crewai-webzio
48
+ export WEBZ_API_TOKEN="your-webz-api-token"
49
+ ```
50
+
51
+ Get a token from your [Webz.io dashboard](https://webz.io) (same token as the News Search API).
52
+
53
+ Requires CrewAI with MCP support (`crewai-tools[mcp]`, installed automatically).
54
+
55
+ Full setup and client options: [MCP Server docs](https://docs.webz.io/docs/webz/news-search-api-mcp).
56
+
57
+ ## With a CrewAI agent
58
+
59
+ ```python
60
+ import os
61
+
62
+ from crewai import Agent, Crew, Task
63
+ from crewai_webzio import WebzioNewsSearchTool
64
+
65
+ with WebzioNewsSearchTool(api_token=os.environ["WEBZ_API_TOKEN"]) as news_tool:
66
+ researcher = Agent(
67
+ role="Research Analyst",
68
+ goal="Find recent news on any topic",
69
+ backstory="Expert researcher with access to Webz.io news search.",
70
+ tools=[news_tool],
71
+ verbose=True,
72
+ )
73
+
74
+ research_task = Task(
75
+ description=(
76
+ "Search Webz news for recent Nvidia supply-chain risk coverage "
77
+ "and summarize with sources."
78
+ ),
79
+ expected_output="A summary with headline, publisher, and URL for each article.",
80
+ agent=researcher,
81
+ )
82
+
83
+ crew = Crew(agents=[researcher], tasks=[research_task], verbose=True)
84
+ print(crew.kickoff())
85
+ ```
86
+
87
+ Use the tool as a context manager (or call `stop()` when done) to shut down the MCP session.
88
+
89
+ ## Direct search (no agent required)
90
+
91
+ ```bash
92
+ export WEBZ_API_TOKEN="your-token"
93
+ python examples/run_news_search.py
94
+ ```
95
+
96
+ ```python
97
+ from crewai_webzio import WebzioNewsSearchTool
98
+
99
+ with WebzioNewsSearchTool() as tool:
100
+ print(sorted(tool.arg_names)) # live filters from MCP tools/list
101
+ print(tool._run(query="EU AI regulation", k=5, days=30))
102
+ ```
103
+
104
+ ## Using Webz via MCP (no package install)
105
+
106
+ CrewAI can connect to the hosted MCP server directly:
107
+
108
+ ```python
109
+ from crewai import Agent
110
+ from crewai.mcp import MCPServerHTTP
111
+
112
+ agent = Agent(
113
+ role="News Researcher",
114
+ goal="Find and summarize current news",
115
+ mcps=[
116
+ MCPServerHTTP(
117
+ url="https://news-search-mcp.webz.io/mcp",
118
+ headers={"Authorization": "Bearer YOUR_WEBZ_API_TOKEN"},
119
+ ),
120
+ ],
121
+ )
122
+ ```
123
+
124
+ ## How it works
125
+
126
+ This package wraps CrewAI's [`MCPServerAdapter`](https://docs.crewai.com/en/mcp/overview) around the hosted Webz News Search MCP server at `https://news-search-mcp.webz.io/mcp`. Each tool call runs a regular News Search API request with your token (same credits and rate limits). Filter fields are not hardcoded — the tool schema comes from the live server.
127
+
128
+ ## Configuration
129
+
130
+ | Name | Default | Purpose |
131
+ | --- | --- | --- |
132
+ | `WEBZ_API_TOKEN` | required | Webz API token from the dashboard |
133
+ | `WEBZ_MCP_URL` | `https://news-search-mcp.webz.io/mcp` | Override for local MCP testing |
134
+
135
+ You can also pass `api_token=` and `mcp_url=` to `WebzioNewsSearchTool()`.
136
+
137
+ ## Links
138
+
139
+ - [Webz.io](https://webz.io)
140
+ - [News Search MCP documentation](https://docs.webz.io/docs/webz/news-search-api-mcp)
141
+ - [MCP server landing page](https://news-search-mcp.webz.io)
142
+ - [News Search API filters](https://docs.webz.io/docs/webz/news-search-api-filters)
143
+ - [CrewAI MCP overview](https://docs.crewai.com/en/mcp/overview)
144
+ - [GitHub](https://github.com/Webhose/webz-news-search)
@@ -0,0 +1,36 @@
1
+ # Publishing crewai-webzio to PyPI
2
+
3
+ ## Prerequisites
4
+
5
+ - PyPI account and API token with upload scope
6
+ - `twine` installed (`pip install twine build`)
7
+
8
+ ## Build
9
+
10
+ ```bash
11
+ cd packages/crewai
12
+ python -m pip install build
13
+ python -m build
14
+ ```
15
+
16
+ Artifacts land in `dist/`:
17
+
18
+ - `crewai_webzio-0.1.0-py3-none-any.whl`
19
+ - `crewai_webzio-0.1.0.tar.gz`
20
+
21
+ ## Upload
22
+
23
+ ```bash
24
+ export TWINE_USERNAME=__token__
25
+ export TWINE_PASSWORD=pypi-xxxxxxxx
26
+ python -m twine upload dist/crewai_webzio-*
27
+ ```
28
+
29
+ Or use `uv publish` if configured.
30
+
31
+ ## Verify
32
+
33
+ ```bash
34
+ pip install crewai-webzio
35
+ python -c "from crewai_webzio import WebzioNewsSearchTool; print(WebzioNewsSearchTool)"
36
+ ```
@@ -0,0 +1,117 @@
1
+ # crewai-webzio
2
+
3
+ **Search global news with [Webz.io](https://webz.io) from [CrewAI](https://crewai.com) agents — in natural language, with the most relevant articles first.**
4
+
5
+ [Webz.io News Search](https://docs.webz.io/docs/webz/news-search-api-mcp) covers news and current events from sources worldwide. Ask a question in plain language, narrow results with filters (language, country, date, sentiment, domain, ticker, and more), and get back focused article excerpts with titles, URLs, and metadata.
6
+
7
+ Use this package with CrewAI agents, or construct the tool directly to verify MCP connectivity.
8
+
9
+ ## What you get
10
+
11
+ - **Natural-language search** — no keyword hacking. Example: `"EU AI Act enforcement updates"` or `"How is Tesla stock reacting to earnings?"`
12
+ - **Worldwide coverage** — semantic search over Webz.io's global news index.
13
+ - **Rich filters** — language, country, days, sentiment, domain, ticker, person, organization, topic, and more. See the [MCP tool reference](https://docs.webz.io/docs/webz/news-search-api-mcp#tool-reference).
14
+ - **Live schema** — filters are loaded from the hosted MCP server (`tools/list`). New Webz filters appear automatically, without republishing this package.
15
+ - **CrewAI tool** — pass `WebzioNewsSearchTool(...)` to `Agent(tools=[...])`.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install crewai-webzio
21
+ export WEBZ_API_TOKEN="your-webz-api-token"
22
+ ```
23
+
24
+ Get a token from your [Webz.io dashboard](https://webz.io) (same token as the News Search API).
25
+
26
+ Requires CrewAI with MCP support (`crewai-tools[mcp]`, installed automatically).
27
+
28
+ Full setup and client options: [MCP Server docs](https://docs.webz.io/docs/webz/news-search-api-mcp).
29
+
30
+ ## With a CrewAI agent
31
+
32
+ ```python
33
+ import os
34
+
35
+ from crewai import Agent, Crew, Task
36
+ from crewai_webzio import WebzioNewsSearchTool
37
+
38
+ with WebzioNewsSearchTool(api_token=os.environ["WEBZ_API_TOKEN"]) as news_tool:
39
+ researcher = Agent(
40
+ role="Research Analyst",
41
+ goal="Find recent news on any topic",
42
+ backstory="Expert researcher with access to Webz.io news search.",
43
+ tools=[news_tool],
44
+ verbose=True,
45
+ )
46
+
47
+ research_task = Task(
48
+ description=(
49
+ "Search Webz news for recent Nvidia supply-chain risk coverage "
50
+ "and summarize with sources."
51
+ ),
52
+ expected_output="A summary with headline, publisher, and URL for each article.",
53
+ agent=researcher,
54
+ )
55
+
56
+ crew = Crew(agents=[researcher], tasks=[research_task], verbose=True)
57
+ print(crew.kickoff())
58
+ ```
59
+
60
+ Use the tool as a context manager (or call `stop()` when done) to shut down the MCP session.
61
+
62
+ ## Direct search (no agent required)
63
+
64
+ ```bash
65
+ export WEBZ_API_TOKEN="your-token"
66
+ python examples/run_news_search.py
67
+ ```
68
+
69
+ ```python
70
+ from crewai_webzio import WebzioNewsSearchTool
71
+
72
+ with WebzioNewsSearchTool() as tool:
73
+ print(sorted(tool.arg_names)) # live filters from MCP tools/list
74
+ print(tool._run(query="EU AI regulation", k=5, days=30))
75
+ ```
76
+
77
+ ## Using Webz via MCP (no package install)
78
+
79
+ CrewAI can connect to the hosted MCP server directly:
80
+
81
+ ```python
82
+ from crewai import Agent
83
+ from crewai.mcp import MCPServerHTTP
84
+
85
+ agent = Agent(
86
+ role="News Researcher",
87
+ goal="Find and summarize current news",
88
+ mcps=[
89
+ MCPServerHTTP(
90
+ url="https://news-search-mcp.webz.io/mcp",
91
+ headers={"Authorization": "Bearer YOUR_WEBZ_API_TOKEN"},
92
+ ),
93
+ ],
94
+ )
95
+ ```
96
+
97
+ ## How it works
98
+
99
+ This package wraps CrewAI's [`MCPServerAdapter`](https://docs.crewai.com/en/mcp/overview) around the hosted Webz News Search MCP server at `https://news-search-mcp.webz.io/mcp`. Each tool call runs a regular News Search API request with your token (same credits and rate limits). Filter fields are not hardcoded — the tool schema comes from the live server.
100
+
101
+ ## Configuration
102
+
103
+ | Name | Default | Purpose |
104
+ | --- | --- | --- |
105
+ | `WEBZ_API_TOKEN` | required | Webz API token from the dashboard |
106
+ | `WEBZ_MCP_URL` | `https://news-search-mcp.webz.io/mcp` | Override for local MCP testing |
107
+
108
+ You can also pass `api_token=` and `mcp_url=` to `WebzioNewsSearchTool()`.
109
+
110
+ ## Links
111
+
112
+ - [Webz.io](https://webz.io)
113
+ - [News Search MCP documentation](https://docs.webz.io/docs/webz/news-search-api-mcp)
114
+ - [MCP server landing page](https://news-search-mcp.webz.io)
115
+ - [News Search API filters](https://docs.webz.io/docs/webz/news-search-api-filters)
116
+ - [CrewAI MCP overview](https://docs.crewai.com/en/mcp/overview)
117
+ - [GitHub](https://github.com/Webhose/webz-news-search)
@@ -0,0 +1,29 @@
1
+ from crewai_webzio.consts import (
2
+ DEFAULT_MCP_URL,
3
+ MCP_TRANSPORT,
4
+ MCP_URL_ENV_NAME,
5
+ PREFERRED_TOOL_NAME,
6
+ TOKEN_ENV_NAME,
7
+ )
8
+ from crewai_webzio.tool import (
9
+ WebzConfigError,
10
+ WebzioNewsSearchTool,
11
+ build_server_params,
12
+ pick_news_search_tool,
13
+ resolve_api_token,
14
+ resolve_mcp_url,
15
+ )
16
+
17
+ __all__ = [
18
+ "DEFAULT_MCP_URL",
19
+ "MCP_TRANSPORT",
20
+ "MCP_URL_ENV_NAME",
21
+ "PREFERRED_TOOL_NAME",
22
+ "TOKEN_ENV_NAME",
23
+ "WebzConfigError",
24
+ "WebzioNewsSearchTool",
25
+ "build_server_params",
26
+ "pick_news_search_tool",
27
+ "resolve_api_token",
28
+ "resolve_mcp_url",
29
+ ]
@@ -0,0 +1,5 @@
1
+ DEFAULT_MCP_URL = "https://news-search-mcp.webz.io/mcp"
2
+ TOKEN_ENV_NAME = "WEBZ_API_TOKEN"
3
+ MCP_URL_ENV_NAME = "WEBZ_MCP_URL"
4
+ PREFERRED_TOOL_NAME = "news_search_by_webz"
5
+ MCP_TRANSPORT = "streamable-http"
@@ -0,0 +1,161 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from types import TracebackType
5
+ from typing import Any
6
+
7
+ from crewai.tools import BaseTool, EnvVar
8
+ from crewai_tools import MCPServerAdapter
9
+ from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
10
+
11
+ from crewai_webzio.consts import (
12
+ DEFAULT_MCP_URL,
13
+ MCP_TRANSPORT,
14
+ MCP_URL_ENV_NAME,
15
+ PREFERRED_TOOL_NAME,
16
+ TOKEN_ENV_NAME,
17
+ )
18
+
19
+
20
+ class WebzConfigError(ValueError):
21
+ """Raised when the Webz MCP client cannot be configured or loaded."""
22
+
23
+
24
+ def resolve_api_token(api_token: str | None = None) -> str:
25
+ token = (api_token or os.getenv(TOKEN_ENV_NAME) or "").strip()
26
+ if not token:
27
+ raise WebzConfigError(
28
+ f"missing Webz API token. set {TOKEN_ENV_NAME} or pass api_token."
29
+ )
30
+ return token
31
+
32
+
33
+ def resolve_mcp_url(mcp_url: str | None = None) -> str:
34
+ url = (mcp_url or os.getenv(MCP_URL_ENV_NAME) or DEFAULT_MCP_URL).strip()
35
+ if not url:
36
+ raise WebzConfigError("missing MCP url.")
37
+ return url.rstrip("/")
38
+
39
+
40
+ def build_server_params(
41
+ api_token: str | None = None,
42
+ *,
43
+ mcp_url: str | None = None,
44
+ ) -> dict[str, Any]:
45
+ token = resolve_api_token(api_token)
46
+ url = resolve_mcp_url(mcp_url)
47
+ return {
48
+ "url": url,
49
+ "transport": MCP_TRANSPORT,
50
+ "headers": {"Authorization": f"Bearer {token}"},
51
+ }
52
+
53
+
54
+ def pick_news_search_tool(tools: list[BaseTool]) -> BaseTool:
55
+ if not tools:
56
+ raise WebzConfigError("MCP server returned no tools.")
57
+ for tool in tools:
58
+ if tool.name == PREFERRED_TOOL_NAME:
59
+ return tool
60
+ if len(tools) == 1:
61
+ return tools[0]
62
+ names = ", ".join(tool.name for tool in tools)
63
+ raise WebzConfigError(
64
+ f"MCP server did not expose {PREFERRED_TOOL_NAME}. available tools: {names}"
65
+ )
66
+
67
+
68
+ class WebzioNewsSearchToolSchema(BaseModel):
69
+ """Minimal static schema; replaced at init with the live MCP schema."""
70
+
71
+ model_config = ConfigDict(extra="allow")
72
+
73
+ query: str = Field(..., description="Natural-language news search query")
74
+
75
+
76
+ class WebzioNewsSearchTool(BaseTool):
77
+ """CrewAI tool that exposes Webz.io contextual news search via the hosted MCP server.
78
+
79
+ Filter fields are loaded live from MCP ``tools/list``. New server filters appear
80
+ automatically at runtime without republishing this package.
81
+
82
+ Use as a context manager or call ``stop()`` when done to shut down the MCP session::
83
+
84
+ with WebzioNewsSearchTool() as tool:
85
+ agent = Agent(..., tools=[tool])
86
+ """
87
+
88
+ name: str = PREFERRED_TOOL_NAME
89
+ description: str = (
90
+ "Search global news with Webz.io. Returns article excerpts with titles, "
91
+ "URLs, and metadata. Filter by language, country, date, sentiment, domain, "
92
+ "ticker, and more."
93
+ )
94
+ args_schema: type[BaseModel] = WebzioNewsSearchToolSchema
95
+ package_dependencies: list[str] = Field(
96
+ default_factory=lambda: ["crewai-tools[mcp]"]
97
+ )
98
+ env_vars: list[EnvVar] = Field(
99
+ default_factory=lambda: [
100
+ EnvVar(
101
+ name=TOKEN_ENV_NAME,
102
+ description="Webz.io API token from the dashboard",
103
+ required=True,
104
+ ),
105
+ EnvVar(
106
+ name=MCP_URL_ENV_NAME,
107
+ description="Override MCP endpoint for testing",
108
+ required=False,
109
+ ),
110
+ ]
111
+ )
112
+
113
+ _adapter: MCPServerAdapter | None = PrivateAttr(default=None)
114
+ _live_tool: BaseTool | None = PrivateAttr(default=None)
115
+
116
+ def __init__(
117
+ self,
118
+ api_token: str | None = None,
119
+ *,
120
+ mcp_url: str | None = None,
121
+ connect_timeout: int = 30,
122
+ **kwargs: Any,
123
+ ) -> None:
124
+ super().__init__(**kwargs)
125
+ self._adapter = MCPServerAdapter(
126
+ build_server_params(api_token, mcp_url=mcp_url),
127
+ PREFERRED_TOOL_NAME,
128
+ connect_timeout=connect_timeout,
129
+ )
130
+ self._live_tool = pick_news_search_tool(list(self._adapter.tools))
131
+ self.args_schema = self._live_tool.args_schema
132
+ self.description = self._live_tool.description
133
+
134
+ @property
135
+ def arg_names(self) -> list[str]:
136
+ """Live argument names from the MCP tool schema."""
137
+ return list(self.args_schema.model_fields.keys())
138
+
139
+ def _run(self, **kwargs: Any) -> str:
140
+ if self._live_tool is None:
141
+ raise WebzConfigError("MCP news search tool is not initialized.")
142
+ result = self._live_tool._run(**kwargs)
143
+ return str(result)
144
+
145
+ def stop(self) -> None:
146
+ """Stop the underlying MCP server connection."""
147
+ if self._adapter is not None:
148
+ self._adapter.stop()
149
+ self._adapter = None
150
+ self._live_tool = None
151
+
152
+ def __enter__(self) -> WebzioNewsSearchTool:
153
+ return self
154
+
155
+ def __exit__(
156
+ self,
157
+ exc_type: type[BaseException] | None,
158
+ exc_val: BaseException | None,
159
+ exc_tb: TracebackType | None,
160
+ ) -> None:
161
+ self.stop()
@@ -0,0 +1,40 @@
1
+ """
2
+ Construct a WebzioNewsSearchTool and run one search against the hosted MCP server.
3
+
4
+ Usage:
5
+ export WEBZ_API_TOKEN="your-token"
6
+ python examples/run_news_search.py
7
+
8
+ Or pass the token in code with WebzioNewsSearchTool(api_token="...").
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import sys
15
+
16
+ from crewai_webzio import WebzioNewsSearchTool
17
+
18
+ QUERY = "recent developments on EU AI regulation"
19
+
20
+
21
+ def main() -> None:
22
+ token = os.getenv("WEBZ_API_TOKEN")
23
+ with (
24
+ WebzioNewsSearchTool(api_token=token)
25
+ if token
26
+ else WebzioNewsSearchTool()
27
+ ) as tool:
28
+ print("tool name:", tool.name)
29
+ print("live args from MCP tools/list:", sorted(tool.arg_names))
30
+ print("---")
31
+ print(tool._run(query=QUERY, k=3), end="\n\n")
32
+ print("SUCCESS ! tool is ready for Agent(tools=[tool])")
33
+
34
+
35
+ if __name__ == "__main__":
36
+ try:
37
+ main()
38
+ except Exception as exc:
39
+ print(f"search failed: {exc}", file=sys.stderr)
40
+ raise SystemExit(1)
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "crewai-webzio"
7
+ version = "0.1.0"
8
+ description = "CrewAI tools for Webz.io News Search - global news in natural language with semantic ranking and rich filters"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ { name = "Webz.io", email = "support@webz.io" },
15
+ ]
16
+ keywords = ["crewai", "webz", "webzio", "news", "mcp", "search"]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
26
+ ]
27
+ dependencies = [
28
+ "crewai>=0.80.0",
29
+ "crewai-tools[mcp]>=0.40.0",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ dev = [
34
+ "pytest>=8.0",
35
+ ]
36
+
37
+ [project.urls]
38
+ Homepage = "https://news-search-mcp.webz.io"
39
+ Documentation = "https://docs.webz.io/docs/webz/news-search-api-mcp"
40
+ Repository = "https://github.com/Webhose/webz-news-search"
41
+ Issues = "https://github.com/Webhose/webz-news-search/issues"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["crewai_webzio"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+ pythonpath = ["."]
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ import pytest
6
+
7
+ from crewai_webzio import WebzioNewsSearchTool
8
+ from crewai_webzio.consts import PREFERRED_TOOL_NAME
9
+
10
+ pytestmark = pytest.mark.skipif(
11
+ not os.getenv("WEBZ_API_TOKEN"),
12
+ reason="WEBZ_API_TOKEN is not set",
13
+ )
14
+
15
+
16
+ def test_live_tool_discovers_news_search_schema() -> None:
17
+ with WebzioNewsSearchTool() as tool:
18
+ assert tool.name == PREFERRED_TOOL_NAME
19
+ assert "query" in tool.arg_names
20
+ assert len(tool.arg_names) > 1
21
+
22
+
23
+ def test_live_tool_runs_news_search() -> None:
24
+ with WebzioNewsSearchTool() as tool:
25
+ result = tool._run(query="EU AI regulation", k=1)
26
+ assert isinstance(result, str)
27
+ assert len(result) > 0
@@ -0,0 +1,156 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from types import SimpleNamespace
5
+ from unittest.mock import MagicMock, patch
6
+
7
+ import pytest
8
+ from crewai.tools import BaseTool
9
+ from pydantic import BaseModel, Field
10
+
11
+ from crewai_webzio.consts import (
12
+ DEFAULT_MCP_URL,
13
+ MCP_TRANSPORT,
14
+ PREFERRED_TOOL_NAME,
15
+ TOKEN_ENV_NAME,
16
+ )
17
+ from crewai_webzio.tool import (
18
+ WebzConfigError,
19
+ WebzioNewsSearchTool,
20
+ build_server_params,
21
+ pick_news_search_tool,
22
+ resolve_api_token,
23
+ resolve_mcp_url,
24
+ )
25
+
26
+ PACKAGE_ROOT = Path(__file__).resolve().parents[1]
27
+ TOOL_SOURCE = (PACKAGE_ROOT / "crewai_webzio" / "tool.py").read_text(encoding="utf-8")
28
+ CONSTS_SOURCE = (PACKAGE_ROOT / "crewai_webzio" / "consts.py").read_text(encoding="utf-8")
29
+
30
+ FILTER_NAMES_OWNED_BY_MCP = (
31
+ "allow_all_dates",
32
+ "exclude_domain",
33
+ "domain_rank_gte",
34
+ "domain_rank_lte",
35
+ "trust_category",
36
+ "political_bias",
37
+ "min_similarity",
38
+ "allow_multiple_chunks_per_article",
39
+ )
40
+
41
+
42
+ class LiveArgsSchema(BaseModel):
43
+ query: str = Field(..., description="Search query")
44
+ k: int = Field(default=10, description="Number of results")
45
+ extra_filter: str = Field(default="x", description="From MCP server")
46
+
47
+
48
+ class FakeLiveTool(BaseTool):
49
+ name: str = PREFERRED_TOOL_NAME
50
+ description: str = "Live MCP news search tool"
51
+ args_schema: type[BaseModel] = LiveArgsSchema
52
+
53
+ def _run(self, **kwargs: object) -> str:
54
+ return f"result:{kwargs.get('query')}"
55
+
56
+
57
+ class FakeMcpAdapter:
58
+ def __init__(
59
+ self,
60
+ serverparams: dict,
61
+ *tool_names: str,
62
+ connect_timeout: int = 30,
63
+ ) -> None:
64
+ self.serverparams = serverparams
65
+ self.tool_names = tool_names
66
+ self.connect_timeout = connect_timeout
67
+ self._stopped = False
68
+ self.tools = [FakeLiveTool()]
69
+
70
+ def stop(self) -> None:
71
+ self._stopped = True
72
+
73
+
74
+ def test_resolve_api_token_requires_value(monkeypatch: pytest.MonkeyPatch) -> None:
75
+ monkeypatch.delenv(TOKEN_ENV_NAME, raising=False)
76
+ with pytest.raises(WebzConfigError, match="missing Webz API token"):
77
+ resolve_api_token()
78
+
79
+
80
+ def test_resolve_api_token_prefers_argument(monkeypatch: pytest.MonkeyPatch) -> None:
81
+ monkeypatch.setenv(TOKEN_ENV_NAME, "from-env")
82
+ assert resolve_api_token(" from-arg ") == "from-arg"
83
+
84
+
85
+ def test_resolve_mcp_url_default_and_override(monkeypatch: pytest.MonkeyPatch) -> None:
86
+ monkeypatch.delenv("WEBZ_MCP_URL", raising=False)
87
+ assert resolve_mcp_url() == DEFAULT_MCP_URL
88
+ assert resolve_mcp_url("https://localhost:8765/mcp/") == "https://localhost:8765/mcp"
89
+
90
+
91
+ def test_build_server_params_uses_bearer_and_streamable_http() -> None:
92
+ params = build_server_params("secret-token", mcp_url="https://example.test/mcp")
93
+ assert params["url"] == "https://example.test/mcp"
94
+ assert params["transport"] == MCP_TRANSPORT
95
+ assert params["headers"]["Authorization"] == "Bearer secret-token"
96
+
97
+
98
+ def test_pick_news_search_tool_prefers_named_tool() -> None:
99
+ preferred = SimpleNamespace(name=PREFERRED_TOOL_NAME)
100
+ other = SimpleNamespace(name="other_tool")
101
+ assert pick_news_search_tool([other, preferred]) is preferred # type: ignore[arg-type]
102
+
103
+
104
+ def test_pick_news_search_tool_falls_back_to_single_tool() -> None:
105
+ only = SimpleNamespace(name="whatever_the_server_exposes")
106
+ assert pick_news_search_tool([only]) is only # type: ignore[arg-type]
107
+
108
+
109
+ def test_pick_news_search_tool_errors_when_preferred_missing_among_many() -> None:
110
+ with pytest.raises(WebzConfigError, match="did not expose"):
111
+ pick_news_search_tool(
112
+ [SimpleNamespace(name="alpha"), SimpleNamespace(name="beta")] # type: ignore[list-item]
113
+ )
114
+
115
+
116
+ @patch("crewai_webzio.tool.MCPServerAdapter", FakeMcpAdapter)
117
+ def test_webzio_news_search_tool_loads_live_schema_and_delegates_run() -> None:
118
+ tool = WebzioNewsSearchTool(api_token="tok", mcp_url="https://example.test/mcp")
119
+ try:
120
+ assert tool.name == PREFERRED_TOOL_NAME
121
+ assert "query" in tool.arg_names
122
+ assert "extra_filter" in tool.arg_names
123
+ assert tool.description == "Live MCP news search tool"
124
+ assert tool._run(query="EU AI", k=3) == "result:EU AI"
125
+ finally:
126
+ tool.stop()
127
+
128
+
129
+ @patch("crewai_webzio.tool.MCPServerAdapter", FakeMcpAdapter)
130
+ def test_webzio_news_search_tool_context_manager_stops_adapter() -> None:
131
+ with WebzioNewsSearchTool(api_token="tok") as tool:
132
+ assert tool._adapter is not None
133
+ adapter = tool._adapter
134
+ assert adapter._stopped is True
135
+ assert tool._adapter is None
136
+
137
+
138
+ def test_wrapper_source_does_not_hardcode_mcp_filters() -> None:
139
+ combined = TOOL_SOURCE + CONSTS_SOURCE
140
+ for name in FILTER_NAMES_OWNED_BY_MCP:
141
+ assert name not in combined, f"wrapper must not hardcode MCP filter {name}"
142
+
143
+
144
+ def test_public_exports() -> None:
145
+ import crewai_webzio
146
+
147
+ for name in (
148
+ "WebzioNewsSearchTool",
149
+ "WebzConfigError",
150
+ "DEFAULT_MCP_URL",
151
+ "TOKEN_ENV_NAME",
152
+ "build_server_params",
153
+ "resolve_api_token",
154
+ "resolve_mcp_url",
155
+ ):
156
+ assert hasattr(crewai_webzio, name)
@@ -0,0 +1,44 @@
1
+ # Upstream contribution to crewAIInc/crewAI
2
+
3
+ This directory contains files ready to copy into a PR against [crewAIInc/crewAI](https://github.com/crewAIInc/crewAI).
4
+
5
+ ## PR checklist
6
+
7
+ 1. Copy `lib/crewai-tools/src/crewai_tools/tools/webzio_tools/webzio_news_search_tool.py` into the fork.
8
+ 2. Add to `lib/crewai-tools/src/crewai_tools/tools/__init__.py`:
9
+ ```python
10
+ from crewai_tools.tools.webzio_tools.webzio_news_search_tool import (
11
+ WebzioNewsSearchTool,
12
+ )
13
+ ```
14
+ and add `"WebzioNewsSearchTool"` to `__all__`.
15
+ 3. Add to `lib/crewai-tools/src/crewai_tools/__init__.py` the same import and export.
16
+ 4. Copy `lib/crewai-tools/tests/tools/webzio_news_search_tool_test.py`.
17
+ 5. Copy `docs/en/tools/search-research/webzionewssearchtool.mdx`.
18
+ 6. Add a card to `docs/en/tools/search-research/overview.mdx` (Search & Research section).
19
+ 7. Register the page in the docs nav (`docs.json` or equivalent Mintlify config).
20
+ 8. Regenerate tool specs:
21
+ ```bash
22
+ cd lib/crewai-tools
23
+ python -m crewai_tools.generate_tool_specs
24
+ ```
25
+ 9. Run tests:
26
+ ```bash
27
+ pytest lib/crewai-tools/tests/tools/webzio_news_search_tool_test.py
28
+ ```
29
+
30
+ No new vendor SDK dependency is required — the tool uses the existing `mcp` extra via `MCPServerAdapter`.
31
+
32
+ ## Suggested PR title
33
+
34
+ `feat(tools): add WebzioNewsSearchTool for Webz.io news search via MCP`
35
+
36
+ ## Suggested PR description
37
+
38
+ - Adds `WebzioNewsSearchTool` wrapping the hosted Webz News Search MCP server
39
+ - Filter schema loaded live from MCP `tools/list` (not hardcoded)
40
+ - Auth via `WEBZ_API_TOKEN` (Bearer) and optional `WEBZ_MCP_URL`
41
+ - Docs page under Search & Research, modeled on ExaSearchTool
42
+ - Unit tests mock `MCPServerAdapter` (no live token in CI)
43
+
44
+ Maintained by Webz.io. Standalone PyPI package: [crewai-webzio](https://pypi.org/project/crewai-webzio/).
@@ -0,0 +1,3 @@
1
+ <Card title="Webzio News Search" icon="newspaper" href="/en/tools/search-research/webzionewssearchtool">
2
+ Webz.io contextual news search with live MCP filters
3
+ </Card>
@@ -0,0 +1,135 @@
1
+ ---
2
+ title: Webzio News Search Tool
3
+ description: Search global news with Webz.io from CrewAI agents. Semantic ranking, rich filters, and live MCP schemas.
4
+ icon: newspaper
5
+ ---
6
+
7
+ # Webzio News Search Tool
8
+
9
+ > Search global news with [Webz.io](https://webz.io), the contextual news search API. Get token-efficient article excerpts with titles, URLs, and metadata.
10
+
11
+ The `WebzioNewsSearchTool` lets CrewAI agents search news and current events using [Webz.io News Search](https://docs.webz.io/docs/webz/news-search-api-mcp). It returns the most relevant articles for any query, with rich filters loaded live from the hosted MCP server.
12
+
13
+ ## Installation
14
+
15
+ Install the CrewAI tools package with MCP support:
16
+
17
+ ```shell
18
+ pip install 'crewai[tools]'
19
+ ```
20
+
21
+ ## Environment Variables
22
+
23
+ Set your Webz API token as an environment variable:
24
+
25
+ ```bash
26
+ export WEBZ_API_TOKEN='your_webz_api_token'
27
+ ```
28
+
29
+ <Note>
30
+ The environment variable name is `WEBZ_API_TOKEN` (not `WEBZ_API_KEY`). Get an API key from the [Webz.io dashboard](https://webz.io).
31
+ </Note>
32
+
33
+ ## Example Usage
34
+
35
+ Here's how to use the `WebzioNewsSearchTool` within a CrewAI agent:
36
+
37
+ ```python
38
+ import os
39
+ from crewai import Agent, Task, Crew
40
+ from crewai_tools import WebzioNewsSearchTool
41
+
42
+ with WebzioNewsSearchTool(api_token=os.environ["WEBZ_API_TOKEN"]) as news_tool:
43
+ researcher = Agent(
44
+ role="Research Analyst",
45
+ goal="Find recent news on any topic",
46
+ backstory="Expert researcher with access to Webz.io news search.",
47
+ tools=[news_tool],
48
+ verbose=True,
49
+ )
50
+
51
+ research_task = Task(
52
+ description="Find the top 3 recent breakthroughs in quantum computing.",
53
+ expected_output="A summary of the top 3 breakthroughs with source URLs.",
54
+ agent=researcher,
55
+ )
56
+
57
+ crew = Crew(agents=[researcher], tasks=[research_task], verbose=True)
58
+ result = crew.kickoff()
59
+ print(result)
60
+ ```
61
+
62
+ Use the tool as a context manager (or call `stop()` when done) to shut down the MCP session.
63
+
64
+ ## Configuration Options
65
+
66
+ The `WebzioNewsSearchTool` accepts the following parameters during initialization:
67
+
68
+ * `api_token` (str, optional): Your Webz API token. Falls back to the `WEBZ_API_TOKEN` environment variable if not provided.
69
+ * `mcp_url` (str, optional): Custom MCP server URL. Falls back to the `WEBZ_MCP_URL` environment variable, then `https://news-search-mcp.webz.io/mcp`.
70
+ * `connect_timeout` (int, optional): Connection timeout in seconds (default: `30`).
71
+
72
+ When calling the tool (or when an agent invokes it), filter parameters are loaded live from MCP `tools/list`. Common parameters include:
73
+
74
+ * `query` (str): **Required**. Natural-language search query.
75
+ * `k` (int, optional): Number of results (1–100, default 10).
76
+ * `days` (int, optional): Lookback window in days.
77
+ * `language` (list[str], optional): e.g. `["english"]`.
78
+ * `country` (list[str], optional): ISO-2 codes, e.g. `["US", "GB"]`.
79
+ * `sentiment` (str, optional): `positive`, `negative`, or `neutral`.
80
+ * `ticker` (list[str], optional): Stock symbols, e.g. `["NVDA"]`.
81
+ * `domain` / `exclude_domain` (list[str], optional): Restrict or exclude publishers.
82
+
83
+ For the full filter reference, see the [Webz MCP tool reference](https://docs.webz.io/docs/webz/news-search-api-mcp#tool-reference).
84
+
85
+ ## Advanced Usage
86
+
87
+ For filtered news research, steer the agent in the task description:
88
+
89
+ ```python
90
+ research_task = Task(
91
+ description=(
92
+ "Search Webz news for analyst reaction to Nvidia earnings. "
93
+ "Use ticker NVDA, the last 7 days, English only, and limit to 5 results."
94
+ ),
95
+ expected_output="Headline, publisher, and URL for each article.",
96
+ agent=researcher,
97
+ )
98
+ ```
99
+
100
+ ## Using Webz via MCP
101
+
102
+ You can also connect your agent to the hosted Webz MCP server directly:
103
+
104
+ ```python
105
+ from crewai import Agent
106
+ from crewai.mcp import MCPServerHTTP
107
+
108
+ agent = Agent(
109
+ role="Research Analyst",
110
+ goal="Find and analyze news on the web",
111
+ backstory="Expert researcher with access to Webz news search",
112
+ mcps=[
113
+ MCPServerHTTP(
114
+ url="https://news-search-mcp.webz.io/mcp",
115
+ headers={"Authorization": "Bearer YOUR_WEBZ_API_TOKEN"},
116
+ ),
117
+ ],
118
+ )
119
+ ```
120
+
121
+ Get your API key from the [Webz.io dashboard](https://webz.io). For more on MCP in CrewAI, see the [MCP overview](/en/mcp/overview).
122
+
123
+ ## Features
124
+
125
+ * **Natural-Language Search**: Ask questions in plain language, not keywords
126
+ * **Global Coverage**: Semantic search over Webz.io's worldwide news index
127
+ * **Rich Filters**: Language, country, date, sentiment, domain, ticker, and more
128
+ * **Live Schema**: New filters ship with the MCP server — no package update required
129
+ * **Token-Efficient Excerpts**: Focused article snippets with titles and URLs
130
+
131
+ ## Resources
132
+
133
+ * [Webz.io News Search MCP documentation](https://docs.webz.io/docs/webz/news-search-api-mcp)
134
+ * [Webz.io dashboard](https://webz.io)
135
+ * [Standalone PyPI package: crewai-webzio](https://pypi.org/project/crewai-webzio/)
@@ -0,0 +1,155 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from types import TracebackType
5
+ from typing import Any
6
+
7
+ from crewai.tools import BaseTool, EnvVar
8
+ from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
9
+
10
+ from crewai_tools.adapters.mcp_adapter import MCPServerAdapter
11
+
12
+ DEFAULT_MCP_URL = "https://news-search-mcp.webz.io/mcp"
13
+ TOKEN_ENV_NAME = "WEBZ_API_TOKEN"
14
+ MCP_URL_ENV_NAME = "WEBZ_MCP_URL"
15
+ PREFERRED_TOOL_NAME = "news_search_by_webz"
16
+ MCP_TRANSPORT = "streamable-http"
17
+
18
+
19
+ class WebzConfigError(ValueError):
20
+ """Raised when the Webz MCP client cannot be configured or loaded."""
21
+
22
+
23
+ def resolve_api_token(api_token: str | None = None) -> str:
24
+ token = (api_token or os.getenv(TOKEN_ENV_NAME) or "").strip()
25
+ if not token:
26
+ raise WebzConfigError(
27
+ f"missing Webz API token. set {TOKEN_ENV_NAME} or pass api_token."
28
+ )
29
+ return token
30
+
31
+
32
+ def resolve_mcp_url(mcp_url: str | None = None) -> str:
33
+ url = (mcp_url or os.getenv(MCP_URL_ENV_NAME) or DEFAULT_MCP_URL).strip()
34
+ if not url:
35
+ raise WebzConfigError("missing MCP url.")
36
+ return url.rstrip("/")
37
+
38
+
39
+ def build_server_params(
40
+ api_token: str | None = None,
41
+ *,
42
+ mcp_url: str | None = None,
43
+ ) -> dict[str, Any]:
44
+ token = resolve_api_token(api_token)
45
+ url = resolve_mcp_url(mcp_url)
46
+ return {
47
+ "url": url,
48
+ "transport": MCP_TRANSPORT,
49
+ "headers": {"Authorization": f"Bearer {token}"},
50
+ }
51
+
52
+
53
+ def pick_news_search_tool(tools: list[BaseTool]) -> BaseTool:
54
+ if not tools:
55
+ raise WebzConfigError("MCP server returned no tools.")
56
+ for tool in tools:
57
+ if tool.name == PREFERRED_TOOL_NAME:
58
+ return tool
59
+ if len(tools) == 1:
60
+ return tools[0]
61
+ names = ", ".join(tool.name for tool in tools)
62
+ raise WebzConfigError(
63
+ f"MCP server did not expose {PREFERRED_TOOL_NAME}. available tools: {names}"
64
+ )
65
+
66
+
67
+ class WebzioNewsSearchToolSchema(BaseModel):
68
+ """Minimal static schema; replaced at init with the live MCP schema."""
69
+
70
+ model_config = ConfigDict(extra="allow")
71
+
72
+ query: str = Field(..., description="Natural-language news search query")
73
+
74
+
75
+ class WebzioNewsSearchTool(BaseTool):
76
+ """Search global news with Webz.io via the hosted News Search MCP server.
77
+
78
+ Filter fields are loaded live from MCP ``tools/list``. New server filters appear
79
+ automatically at runtime without republishing crewai-tools.
80
+
81
+ Use as a context manager or call ``stop()`` when done to shut down the MCP session.
82
+ """
83
+
84
+ name: str = PREFERRED_TOOL_NAME
85
+ description: str = (
86
+ "Search global news with Webz.io. Returns article excerpts with titles, "
87
+ "URLs, and metadata. Filter by language, country, date, sentiment, domain, "
88
+ "ticker, and more."
89
+ )
90
+ args_schema: type[BaseModel] = WebzioNewsSearchToolSchema
91
+ package_dependencies: list[str] = Field(default_factory=lambda: ["mcp"])
92
+ env_vars: list[EnvVar] = Field(
93
+ default_factory=lambda: [
94
+ EnvVar(
95
+ name=TOKEN_ENV_NAME,
96
+ description="Webz.io API token from the dashboard",
97
+ required=True,
98
+ ),
99
+ EnvVar(
100
+ name=MCP_URL_ENV_NAME,
101
+ description="Override MCP endpoint for testing",
102
+ required=False,
103
+ ),
104
+ ]
105
+ )
106
+
107
+ _adapter: MCPServerAdapter | None = PrivateAttr(default=None)
108
+ _live_tool: BaseTool | None = PrivateAttr(default=None)
109
+
110
+ def __init__(
111
+ self,
112
+ api_token: str | None = None,
113
+ *,
114
+ mcp_url: str | None = None,
115
+ connect_timeout: int = 30,
116
+ **kwargs: Any,
117
+ ) -> None:
118
+ super().__init__(**kwargs)
119
+ self._adapter = MCPServerAdapter(
120
+ build_server_params(api_token, mcp_url=mcp_url),
121
+ PREFERRED_TOOL_NAME,
122
+ connect_timeout=connect_timeout,
123
+ )
124
+ self._live_tool = pick_news_search_tool(list(self._adapter.tools))
125
+ self.args_schema = self._live_tool.args_schema
126
+ self.description = self._live_tool.description
127
+
128
+ @property
129
+ def arg_names(self) -> list[str]:
130
+ """Live argument names from the MCP tool schema."""
131
+ return list(self.args_schema.model_fields.keys())
132
+
133
+ def _run(self, **kwargs: Any) -> str:
134
+ if self._live_tool is None:
135
+ raise WebzConfigError("MCP news search tool is not initialized.")
136
+ result = self._live_tool._run(**kwargs)
137
+ return str(result)
138
+
139
+ def stop(self) -> None:
140
+ """Stop the underlying MCP server connection."""
141
+ if self._adapter is not None:
142
+ self._adapter.stop()
143
+ self._adapter = None
144
+ self._live_tool = None
145
+
146
+ def __enter__(self) -> WebzioNewsSearchTool:
147
+ return self
148
+
149
+ def __exit__(
150
+ self,
151
+ exc_type: type[BaseException] | None,
152
+ exc_val: BaseException | None,
153
+ exc_tb: TracebackType | None,
154
+ ) -> None:
155
+ self.stop()
@@ -0,0 +1,102 @@
1
+ from __future__ import annotations
2
+
3
+ from types import SimpleNamespace
4
+ from unittest.mock import patch
5
+
6
+ import pytest
7
+ from crewai.tools import BaseTool
8
+ from pydantic import BaseModel, Field
9
+
10
+ from crewai_tools.tools.webzio_tools.webzio_news_search_tool import (
11
+ PREFERRED_TOOL_NAME,
12
+ TOKEN_ENV_NAME,
13
+ WebzConfigError,
14
+ WebzioNewsSearchTool,
15
+ build_server_params,
16
+ pick_news_search_tool,
17
+ resolve_api_token,
18
+ resolve_mcp_url,
19
+ )
20
+
21
+
22
+ class LiveArgsSchema(BaseModel):
23
+ query: str = Field(..., description="Search query")
24
+ k: int = Field(default=10, description="Number of results")
25
+ extra_filter: str = Field(default="x", description="From MCP server")
26
+
27
+
28
+ class FakeLiveTool(BaseTool):
29
+ name: str = PREFERRED_TOOL_NAME
30
+ description: str = "Live MCP news search tool"
31
+ args_schema: type[BaseModel] = LiveArgsSchema
32
+
33
+ def _run(self, **kwargs: object) -> str:
34
+ return f"result:{kwargs.get('query')}"
35
+
36
+
37
+ class FakeMcpAdapter:
38
+ def __init__(
39
+ self,
40
+ serverparams: dict,
41
+ *tool_names: str,
42
+ connect_timeout: int = 30,
43
+ ) -> None:
44
+ self.serverparams = serverparams
45
+ self.tool_names = tool_names
46
+ self.connect_timeout = connect_timeout
47
+ self._stopped = False
48
+ self.tools = [FakeLiveTool()]
49
+
50
+ def stop(self) -> None:
51
+ self._stopped = True
52
+
53
+
54
+ def test_resolve_api_token_requires_value(monkeypatch: pytest.MonkeyPatch) -> None:
55
+ monkeypatch.delenv(TOKEN_ENV_NAME, raising=False)
56
+ with pytest.raises(WebzConfigError, match="missing Webz API token"):
57
+ resolve_api_token()
58
+
59
+
60
+ @patch(
61
+ "crewai_tools.tools.webzio_tools.webzio_news_search_tool.MCPServerAdapter",
62
+ FakeMcpAdapter,
63
+ )
64
+ def test_webzio_news_search_tool_loads_live_schema_and_delegates_run() -> None:
65
+ tool = WebzioNewsSearchTool(api_token="tok", mcp_url="https://example.test/mcp")
66
+ try:
67
+ assert tool.name == PREFERRED_TOOL_NAME
68
+ assert "query" in tool.arg_names
69
+ assert "extra_filter" in tool.arg_names
70
+ assert tool._run(query="EU AI", k=3) == "result:EU AI"
71
+ finally:
72
+ tool.stop()
73
+
74
+
75
+ @patch(
76
+ "crewai_tools.tools.webzio_tools.webzio_news_search_tool.MCPServerAdapter",
77
+ FakeMcpAdapter,
78
+ )
79
+ def test_webzio_news_search_tool_context_manager_stops_adapter() -> None:
80
+ with WebzioNewsSearchTool(api_token="tok") as tool:
81
+ assert tool._adapter is not None
82
+ adapter = tool._adapter
83
+ assert adapter._stopped is True
84
+
85
+
86
+ def test_pick_news_search_tool_prefers_named_tool() -> None:
87
+ preferred = SimpleNamespace(name=PREFERRED_TOOL_NAME)
88
+ other = SimpleNamespace(name="other_tool")
89
+ assert pick_news_search_tool([other, preferred]) is preferred # type: ignore[arg-type]
90
+
91
+
92
+ def test_build_server_params_uses_bearer_header() -> None:
93
+ params = build_server_params("secret-token", mcp_url="https://example.test/mcp")
94
+ assert params["headers"]["Authorization"] == "Bearer secret-token"
95
+ assert params["transport"] == "streamable-http"
96
+
97
+
98
+ def test_deprecated_alias_excluded_from_tool_specs() -> None:
99
+ from crewai_tools.generate_tool_specs import ToolSpecExtractor
100
+
101
+ names = {tool["name"] for tool in ToolSpecExtractor().extract_all_tools()}
102
+ assert "WebzioNewsSearchTool" in names