diff --git a/plugins/communication_protocols/mcp/README.md b/plugins/communication_protocols/mcp/README.md index 189296d..65a3992 100644 --- a/plugins/communication_protocols/mcp/README.md +++ b/plugins/communication_protocols/mcp/README.md @@ -82,6 +82,45 @@ result = await client.call_tool("mcp_server.filesystem.read_file", { } ``` +### Free Web Search and Page Fetching with Parallel + +Use [Parallel Search MCP](https://docs.parallel.ai/integrations/mcp/search-mcp) +to search the web and fetch page excerpts without an account or API key. +Anonymous search uses Fast mode and has lower rate limits than authenticated +access. Add this server to an MCP manual's `config.mcpServers`: + +```json +{ + "search": { + "transport": "http", + "url": "https://search.parallel.ai/mcp", + "headers": {"User-Agent": "python-utcp/parallel-search-example"} + } +} +``` + +The [runnable example](examples/parallel_search.py) creates a manual named +`parallel`, discovers its tools, and calls `parallel.search.web_search` and +`parallel.search.web_fetch` through `UtcpClient.call_tool`. It prints search +results and fetched page content, then closes the client. It is an opt-in +example and does not change existing client configuration. + +From the repository root, install the core and MCP plugin in a Python 3.11+ +virtual environment and run: + +```bash +python -m venv .venv +# Activate .venv using your shell's activation command, then: +python -m pip install -e core -e plugins/communication_protocols/mcp +python plugins/communication_protocols/mcp/examples/parallel_search.py +# Or supply a search query and a specific page to fetch: +python plugins/communication_protocols/mcp/examples/parallel_search.py "Python asyncio documentation" --url https://docs.python.org/3/library/asyncio.html +``` + +The example calls the tools directly; no LLM or model credentials are needed. +For an agent, use the discovered input schemas to construct tool arguments and +reuse a stable `session_id` across search and fetch calls in the conversation. + ### With OAuth2 Authentication ```json { diff --git a/plugins/communication_protocols/mcp/examples/parallel_search.py b/plugins/communication_protocols/mcp/examples/parallel_search.py new file mode 100644 index 0000000..fae98d1 --- /dev/null +++ b/plugins/communication_protocols/mcp/examples/parallel_search.py @@ -0,0 +1,60 @@ +"""Search the web and fetch a page through UTCP's HTTP MCP plugin.""" + +import argparse +import asyncio +import json +import uuid + +from utcp.utcp_client import UtcpClient + + +CONFIG = { + "manual_call_templates": [{ + "name": "parallel", + "call_template_type": "mcp", + "config": { + "mcpServers": { + "search": { + "transport": "http", + "url": "https://search.parallel.ai/mcp", + "headers": {"User-Agent": "python-utcp/parallel-search-example"}, + } + } + }, + }] +} + + +async def main(query: str, url: str) -> None: + client = await UtcpClient.create(config=CONFIG) + try: + tools = await client.config.tool_repository.get_tools() + print("Discovered tools:", ", ".join(tool.name for tool in tools)) + + # Reuse one anonymous session identifier for both calls in this run. + session_id = str(uuid.uuid4()) + search = await client.call_tool("parallel.search.web_search", { + "objective": query, + "search_queries": [query], + "session_id": session_id, + }) + print("Search result:") + print(json.dumps(search, indent=2, ensure_ascii=False)) + + page = await client.call_tool("parallel.search.web_fetch", { + "urls": [url], + "session_id": session_id, + }) + print("Fetched page:") + print(json.dumps(page, indent=2, ensure_ascii=False)) + finally: + await client.close() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("query", nargs="?", default="UTCP Python MCP plugin") + parser.add_argument("--url", default="https://www.utcp.io/", + help="Page to fetch after the search") + args = parser.parse_args() + asyncio.run(main(args.query, args.url))