Tutorial

How to Build a Multi-Backend Search Extension for Coding Agents

Build a search extension for coding agents that queries multiple backends in parallel with failover. Normalize results from Scavio, Brave, and Tavily.

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.

Python
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.

Python
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.

Python
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.

Python
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

Python
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

JavaScript
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

JSON
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)

Related Tutorials

Frequently Asked Questions

Most developers complete this tutorial in 15 to 30 minutes. You will need a Scavio API key (free tier works) and a working Python or JavaScript environment.

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. A Scavio API key gives you 250 free credits per month.

Yes. The free tier includes 250 credits per month, which is more than enough to complete this tutorial and prototype a working solution.

Scavio has a native LangChain package (langchain-scavio), an MCP server, and a plain REST API that works with any HTTP client. This tutorial uses the raw REST API, but you can adapt to your framework of choice.

Start Building

Build a search extension for coding agents that queries multiple backends in parallel with failover. Normalize results from Scavio, Brave, and Tavily.