Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions plugins/communication_protocols/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
60 changes: 60 additions & 0 deletions plugins/communication_protocols/mcp/examples/parallel_search.py
Original file line number Diff line number Diff line change
@@ -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))