diff --git a/docs/webui.md b/docs/webui.md index f6efd3e0..ab9e0adc 100644 --- a/docs/webui.md +++ b/docs/webui.md @@ -187,6 +187,11 @@ extraction tools without requiring an API key. This does not replace nanobot's built-in web search provider; mention the Firecrawl MCP preset with `@` when a turn needs Firecrawl's richer web data tools. +The Parallel Search preset connects to the free, anonymous Parallel Search MCP +endpoint and exposes `web_search` and `web_fetch` without requiring an API key. +It is an optional integration and does not replace nanobot's built-in web search +provider; mention `@parallel-search` when a turn should use it. + After an App or integration is available, mention it from the composer with `@` to attach that tool to the next message. diff --git a/nanobot/webui/mcp_presets_api.py b/nanobot/webui/mcp_presets_api.py index 5c3f31b8..0521848f 100644 --- a/nanobot/webui/mcp_presets_api.py +++ b/nanobot/webui/mcp_presets_api.py @@ -188,6 +188,25 @@ MCP_PRESETS: tuple[McpPreset, ...] = ( "the built-in preset; use a custom MCP server URL if you want account-specific limits." ), ), + McpPreset( + name="parallel-search", + display_name="Parallel Search", + category="web", + description="Search the web and fetch relevant page content through Parallel's hosted MCP server.", + docs_url="https://docs.parallel.ai/integrations/mcp/search-mcp", + transport="streamableHttp", + install_supported=True, + brand_domain="parallel.ai", + brand_color="#111827", + requires="Network access", + server=MCPServerConfig( + type="streamableHttp", + url="https://search.parallel.ai/mcp", + tool_timeout=60, + enabled_tools=["web_search", "web_fetch"], + ), + note="Free anonymous access for light use; no API key is required.", + ), McpPreset( name="exa", display_name="Exa", diff --git a/tests/webui/test_mcp_presets_api.py b/tests/webui/test_mcp_presets_api.py index f1bd12e0..e2a2a668 100644 --- a/tests/webui/test_mcp_presets_api.py +++ b/tests/webui/test_mcp_presets_api.py @@ -32,6 +32,7 @@ def test_mcp_presets_payload_lists_supported_cards(tmp_path, monkeypatch: pytest "figma", "context7", "firecrawl", + "parallel-search", "exa", "microsoft-learn", "aws-docs", @@ -136,11 +137,13 @@ def test_enable_no_auth_remote_presets_write_url(tmp_path, monkeypatch: pytest.M mcp_presets_action("enable", {"name": ["microsoft-learn"]}) mcp_presets_action("enable", {"name": ["exa"]}) mcp_presets_action("enable", {"name": ["firecrawl"]}) + mcp_presets_action("enable", {"name": ["parallel-search"]}) config = load_config() assert config.tools.mcp_servers["microsoft-learn"].url == "https://learn.microsoft.com/api/mcp" assert config.tools.mcp_servers["exa"].url == "https://mcp.exa.ai/mcp" assert config.tools.mcp_servers["firecrawl"].url == "https://mcp.firecrawl.dev/v2/mcp" + assert config.tools.mcp_servers["parallel-search"].url == "https://search.parallel.ai/mcp" def test_firecrawl_preset_is_keyless(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None: @@ -160,6 +163,28 @@ def test_firecrawl_preset_is_keyless(tmp_path, monkeypatch: pytest.MonkeyPatch) assert config.tools.mcp_servers["firecrawl"].env == {} +def test_parallel_search_preset_is_keyless_and_tool_limited( + tmp_path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + _use_config(tmp_path, monkeypatch) + + payload = mcp_presets_action("enable", {"name": ["parallel-search"]}) + + row = next(item for item in payload["presets"] if item["name"] == "parallel-search") + assert row["transport"] == "streamableHttp" + assert row["requires"] == "Network access" + assert row["required_fields"] == [] + assert row["configured"] is True + assert "no API key" in row["note"] + config = load_config() + server = config.tools.mcp_servers["parallel-search"] + assert server.type == "streamableHttp" + assert server.url == "https://search.parallel.ai/mcp" + assert server.enabled_tools == ["web_search", "web_fetch"] + assert server.headers == {} + + def test_remove_mcp_preset_updates_config(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None: _use_config(tmp_path, monkeypatch) mcp_presets_action("enable", {"name": ["playwright"]})