Coding agents like Aider, Continue, and Cursor need web search for documentation lookups, package validation, and API reference checks. A single search backend creates a single point of failure. This tutorial builds a multi-backend search extension that fires parallel requests to Scavio, Brave, and Tavily, returns the fastest response, and falls back to the next provider if one fails. The extension exposes a unified interface that any coding agent can consume.
Prerequisites
- Python 3.9+ installed
- requests library installed
- API keys for Scavio and at least one backup provider
- A coding agent that supports custom tool extensions
Walkthrough
Step 1: Set up the parallel search executor
Use concurrent.futures to fire search requests to multiple providers simultaneously. The first successful response wins.
import os, requests, time
from concurrent.futures import ThreadPoolExecutor, as_completed
SCAVIO_KEY = os.environ.get('SCAVIO_API_KEY', '')
BRAVE_KEY = os.environ.get('BRAVE_API_KEY', '')
TAVILY_KEY = os.environ.get('TAVILY_API_KEY', '')
def search_scavio(query: str) -> list:
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': 'us', 'num_results': 5}, timeout=8)
resp.raise_for_status()
return [{'title': r['title'], 'url': r['link'], 'snippet': r.get('snippet', ''), 'source': 'scavio'}
for r in resp.json().get('organic_results', [])]
def search_brave(query: str) -> list:
resp = requests.get('https://api.search.brave.com/res/v1/web/search',
headers={'X-Subscription-Token': BRAVE_KEY},
params={'q': query, 'count': 5}, timeout=8)
resp.raise_for_status()
return [{'title': r['title'], 'url': r['url'], 'snippet': r.get('description', ''), 'source': 'brave'}
for r in resp.json().get('web', {}).get('results', [])]Step 2: Build the parallel executor with failover
Execute all providers in parallel and return the first successful result. Log which provider served the request for debugging.
PROVIDERS = [
('scavio', search_scavio),
('brave', search_brave),
]
def parallel_search(query: str, timeout: float = 10.0) -> dict:
"""Search multiple providers in parallel, return first success."""
with ThreadPoolExecutor(max_workers=len(PROVIDERS)) as executor:
futures = {executor.submit(fn, query): name for name, fn in PROVIDERS}
for future in as_completed(futures, timeout=timeout):
provider = futures[future]
try:
results = future.result()
if results:
return {'results': results, 'provider': provider, 'query': query}
except Exception as e:
print(f'{provider} failed: {e}')
return {'results': [], 'provider': 'none', 'query': query}
result = parallel_search('python requests library documentation')
print(f'Provider: {result["provider"]}')
for r in result['results'][:3]:
print(f' {r["title"]}')
print(f' {r["url"]}')Step 3: Format results for coding agents
Format search results in a way that coding agents can parse: include URLs for reference, code-focused snippets, and package registry indicators.
def format_for_agent(search_result: dict) -> str:
lines = [f'Search results for: {search_result["query"]}', '']
for i, r in enumerate(search_result['results'], 1):
is_docs = any(d in r['url'] for d in ['docs.', 'readthedocs', 'pypi.org', 'npmjs.com', 'github.com'])
prefix = '[DOCS] ' if is_docs else ''
lines.append(f'{i}. {prefix}{r["title"]}')
lines.append(f' URL: {r["url"]}')
if r['snippet']:
lines.append(f' {r["snippet"][:150]}')
lines.append('')
lines.append(f'(served by {search_result["provider"]})')
return '\n'.join(lines)
formatted = format_for_agent(result)
print(formatted)Step 4: Expose as a tool extension
Create a tool definition that any MCP-compatible coding agent can use. The tool accepts a query and returns formatted search results.
TOOL_DEFINITION = {
'name': 'multi_search',
'description': 'Search the web using multiple providers with automatic failover. Useful for documentation, package lookups, and API references.',
'inputSchema': {
'type': 'object',
'properties': {
'query': {'type': 'string', 'description': 'Search query'},
'focus': {'type': 'string', 'enum': ['docs', 'code', 'general'], 'description': 'Search focus area'}
},
'required': ['query']
}
}
def handle_tool_call(args: dict) -> str:
query = args['query']
focus = args.get('focus', 'general')
if focus == 'docs':
query = f'{query} documentation'
elif focus == 'code':
query = f'{query} code example'
result = parallel_search(query)
return format_for_agent(result)
# Test
print(handle_tool_call({'query': 'httpx async client', 'focus': 'docs'}))Python Example
import os, requests
from concurrent.futures import ThreadPoolExecutor, as_completed
SCAVIO_KEY = os.environ.get('SCAVIO_API_KEY', '')
BRAVE_KEY = os.environ.get('BRAVE_API_KEY', '')
def search_scavio(q):
r = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'},
json={'query': q, 'country_code': 'us', 'num_results': 5}, timeout=8)
return [{'title': x['title'], 'url': x['link']} for x in r.json().get('organic_results', [])]
def search_brave(q):
r = requests.get('https://api.search.brave.com/res/v1/web/search',
headers={'X-Subscription-Token': BRAVE_KEY}, params={'q': q, 'count': 5}, timeout=8)
return [{'title': x['title'], 'url': x['url']} for x in r.json().get('web', {}).get('results', [])]
def multi_search(query):
with ThreadPoolExecutor(max_workers=2) as ex:
futs = {ex.submit(fn, query): n for n, fn in [('scavio', search_scavio), ('brave', search_brave)]}
for f in as_completed(futs, timeout=10):
try:
results = f.result()
if results:
print(f'Served by {futs[f]}')
return results
except: pass
return []
for r in multi_search('httpx documentation')[:3]:
print(f' {r["title"]}')JavaScript Example
const SCAVIO_KEY = process.env.SCAVIO_API_KEY;
const BRAVE_KEY = process.env.BRAVE_API_KEY;
async function searchScavio(query) {
const resp = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, country_code: 'us', num_results: 5 })
});
return (await resp.json()).organic_results?.map(r => ({ title: r.title, url: r.link })) || [];
}
async function searchBrave(query) {
const resp = await fetch(`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=5`, {
headers: { 'X-Subscription-Token': BRAVE_KEY }
});
return (await resp.json()).web?.results?.map(r => ({ title: r.title, url: r.url })) || [];
}
async function multiSearch(query) {
const result = await Promise.any([searchScavio(query), searchBrave(query)]);
result.slice(0, 3).forEach(r => console.log(` ${r.title}`));
}
multiSearch('httpx async documentation');Expected Output
Provider: scavio
Python Requests Library Documentation
https://docs.python-requests.org/en/latest/
httpx - A Modern HTTP Client for Python
https://www.python-httpx.org/
1. [DOCS] Python Requests Library Documentation
URL: https://docs.python-requests.org/en/latest/
Requests is an elegant and simple HTTP library for Python...
(served by scavio)