Skip to main content
Web-search provider plugins register a backend that services web_search, web_extract, and (optionally) deep-crawl tool calls. Built-in providers — Firecrawl, SearXNG, Tavily, Perplexity, Exa, Parallel, Keenable, Brave Search (free tier), xAI, and DDGS — all ship as plugins under plugins/web/<name>/. You can add a new one, or override a bundled one, by dropping a directory next to them.
Web search is one of several backend plugins Mibyan supports. The others (with their own ABCs) are Image Generation Provider Plugins, Video Generation Provider Plugins, Memory Provider Plugins, Context Engine Plugins, and Model Provider Plugins. General tool/hook/CLI plugins live in Build a Mibyan Plugin.

How discovery works

Mibyan scans for web-search backends in three places:
  1. Bundled — <repo>/plugins/web/<name>/ (auto-loaded with kind: backend, always available)
  2. User — ~/.mibyan/plugins/web/<name>/ (opt-in via plugins.enabled or mibyan plugins enable <name>)
  3. Pip — packages declaring a mibyan_agent.plugins entry point
Each plugin’s register(ctx) function calls ctx.register_web_search_provider(...) — that puts the instance into the registry in agent/web_search_registry.py. The active provider for each capability is picked by config: When neither key is set, Mibyan auto-detects the backend from whichever API key/URL is present in the environment. mibyan tools walks users through selection.

Directory structure

brave_free/ and ddgs/ are the smallest in-tree references — brave_free for an API-key-gated search-only provider, ddgs for a no-key provider that lazy-installs its SDK.

The WebSearchProvider ABC

Subclass agent.web_search_provider.WebSearchProvider. The only required members are name, is_available(), and whichever of search() / extract() you implement. (Deep crawling is not a separate method — it’s a mode of extract().)

plugin.yaml

ABC reference

Full contract in agent/web_search_provider.py. Methods you may override: Providers can advertise multiple capabilities from a single class — Firecrawl, Tavily, Perplexity, Keenable, Exa, and Parallel all implement both search and extract. Brave Search and DDGS are search-only; SearXNG is search-only with a documented “pair me with an extract provider” workflow.

Response shape

The tool wrapper expects a fixed envelope so it doesn’t have to translate between backends. Search success:
Extract success:
Either capability, on failure:
Both search() and extract() may be async def — the dispatcher detects coroutine functions via inspect.iscoroutinefunction and awaits accordingly. Sync implementations that do blocking I/O (HTTP, SDK calls) are fine for small backends; the dispatcher handles threading.

Capability flags

Mibyan routes calls to the right provider based on the supports_* flags. A common multi-provider setup:
When web.search_backend or web.extract_backend aren’t set, both fall through to web.backend. When that’s also unset, Mibyan picks the first available provider that supports the requested capability based on env-var presence. If your provider only supports one capability, leave the other flags at their default (False) and the registry will skip it for that tool — users won’t see misleading “provider X failed” errors when they’re using X only for search and asking the agent to extract.

How Mibyan wires it into the tools

The web_search and web_extract tools live in tools/web_tools.py. At call time they:
  1. Read the relevant config key (web.search_backend for web_search, web.extract_backend for web_extract)
  2. Ask the registry for the provider with that name
  3. Check is_available() and the matching supports_*() flag
  4. Dispatch to search() / extract() (deep crawl runs as a mode inside extract()), awaiting if the method is a coroutine
  5. JSON-serialize the response envelope and hand it back to the LLM
Errors surface as the tool result; the LLM decides how to explain them. If no provider is registered (or every available one fails the capability gate), the tool returns a helpful error pointing at mibyan tools.

Lazy-installing optional dependencies

Keep availability checks read-only. For an SDK covered by a Mibyan extra, use pm.available("extra-name") in is_available(). Request pm.ensure_import("extra-name") from the operation that needs it. Report InstallError, including a required restart, to the caller. Declare a third-party plugin’s own dependencies in its manifest or pyproject.toml rather than inventing a Mibyan extra. See Build a Mibyan Plugin → Lazy-install.

Reference implementations

  • plugins/web/brave_free/ — small, API-key-gated, search-only HTTP provider. Good starting template.
  • plugins/web/ddgs/ — no-key provider that lazy-installs its SDK. Useful pattern for backends that wrap a Python package.
  • plugins/web/firecrawl/ — full multi-capability provider (search + extract + crawl) with multiple format modes.
  • plugins/web/searxng/ — self-hosted, URL-configured backend with no auth.
  • plugins/web/xai/ — LLM-backed search via Grok’s server-side web_search tool. Shows how to reuse an existing OAuth/env-var credential surface (tools/xai_http.py) without adding new env vars, and how to write a cheap is_available() that honors the no-network contract.

Distribute via pip

my_backend_web_package must expose a top-level register function. See Distribute via pip in the general plugin guide for the full setup.