agent-tools
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@agent-toolssearch the web for the latest AI agent frameworks news"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
agent-tools
agent-tools is a multi-function MCP tool aggregation server. It exposes a single Streamable HTTP MCP endpoint hosting multiple tool families. The first family is web search (aggregating Brave → Exa → SearXNG with multi-key rotation and cross-provider fallback). Additional families (URL fetch, code search, image search, RAG retrieval, etc.) can be added under
src/agent_tools/families/<name>/without changing the server shell, auth, or observability layer.
agent-tools is written in Python 3.12 using the official Python MCP SDK (mcp==1.27.2) and FastMCP. It is consumed by Open WebUI 0.11.0 over Streamable HTTP only — no stdio, no legacy SSE.
License: MIT
Default endpoint:
http://0.0.0.0:8765/mcpContainer bind:
0.0.0.0:8765(host-side loopback isolation is enforced bydocker run -p 127.0.0.1:8765:8765)Logs: JSONL to stderr only (stdout is reserved for MCP protocol frames)
Install
Run with Docker (recommended)
docker buildx build --load -f Dockerfile -t agent-tools:latest .
docker run -d --name agent-tools \
-p 127.0.0.1:8765:8765 \
--env-file config/agent-tools/tools.env \
--env-file config/agent-tools/tools.secrets.env \
-v "$PWD/examples/backends.yaml:/app/config/backends.yaml:ro" \
agent-tools:latestThe image listens on 0.0.0.0:8765/mcp; bind to loopback on the host with -p 127.0.0.1:8765:8765.
Run from source
Requires Python 3.12 and uv.
uv sync --frozen
.venv/bin/python -m agent_toolsRelated MCP server: MCP Server Metasearch
Configuration
Configuration is split into two layers:
Environment variables — bind settings and secrets.
backends.yaml— operator-tunable per-backend policy (chain order, cooldowns, regex safety gate). No secrets live here.
Environment variables
Variable | Required | Default | Description |
| no |
| Bind address (container-internal; publish via |
| no |
| Bind port. |
| no |
| MCP Streamable HTTP mount path. |
| no |
| Python log level. |
| yes* | — | Comma-separated Brave Search API keys. |
| yes* | — | Comma-separated Exa API keys. |
| yes* |
| Comma-separated SearXNG instance base URLs. |
| no |
| Enable bearer-token auth on the MCP endpoint. |
| no | — | Bearer token when |
| no |
| Path to the backends policy file. |
* At least one backend must have at least one key/URL configured for the server to serve traffic; the server itself starts regardless so the failure is visible to MCP clients rather than as a crash loop.
backends.yaml
See examples/backends.yaml for the full annotated sample. Key fields:
chain_order: [brave, exa, searxng]
fallback_on_empty: false # healthy empty result is a success by default
request_timeout_seconds: 10
max_provider_attempts: 3
backends:
brave:
rate_limit_patterns: ["429", "rate.?limit", "quota"]
cooldown_seconds: 300
suspend_on_monthly_exhausted: true
exa:
rate_limit_patterns: ["429", "rate.?limit"]
cooldown_seconds: 60
searxng:
rate_limit_patterns: [] # no rate-limit body patterns by default
cooldown_seconds: 30
partial_on_unresponsive_engines: trueSafety gate (invariant). rate_limit_patterns is the only signal used to classify an error as retryable. An unmatched error (for example a provider returning isError: true with body "no results") is terminal — it does not trigger key rotation or cross-backend fallback. This prevents infinite retry loops.
Secrets hygiene
The repository ships no secrets. The example backends file contains no keys. Put API keys only in an --env-file or your secret manager. Anything matching *.secrets.env is gitignored.
Tool reference
All four tools share the same input schema and return the same WebSearchOutput. They differ only in which backends are consulted.
web_search
Canonical aggregated web search. Walks chain_order (brave → exa → searxng by default), rotating keys within each backend and falling over to the next backend on hard failures. Set backend: "brave" | "exa" | "searxng" in the input to force a single provider; the default is backend: "auto".
web_search_brave
Force Brave Search only. Bypasses the fallback chain but still uses the Brave token pool for multi-key rotation.
web_search_exa
Force Exa only. Bypasses the fallback chain but still uses the Exa token pool. 402 TEAM_BUDGET_EXCEEDED suspends the whole pool; 402 NO_MORE_CREDITS / API_KEY_BUDGET_EXCEEDED suspends only the current key.
web_search_searxng
Force a self-hosted SearXNG instance only. Bypasses the fallback chain. The "key" in the pool is the instance base URL; SearXNG has no API-key contract. The instance must be configured with search.formats: [html, json] or agent-tools will classify the response as config_error.
Input schema (WebSearchInput)
Field | Type | Default | Notes |
|
| required | Max 400 chars (Brave clamps). |
|
|
| Brave clamps to 20; Exa to 100; SearXNG ignores (instance controls page size). |
|
|
| Brave treats this as a 0-indexed page index (offset 0-9); Exa ignores beyond page 1; SearXNG maps to |
|
|
| BCP-47-ish language tag. |
|
|
| SearXNG supports day/month/year; |
|
|
| Mapped per provider. |
|
|
| Exa only; Brave/SearXNG record as |
|
|
| Exa only; Brave/SearXNG record as |
|
|
| Force a single backend. |
|
|
| If true, chain to the next backend when the selected one returns a healthy empty; |
Output schema (WebSearchOutput)
Field | Type | Notes |
|
| Echoes the input query. |
|
| The backend that returned the result. |
|
| Canonical result list. |
|
| Provider-reported total when available. |
|
| Wall-clock elapsed for the full chain. |
|
| Backends attempted in order. |
|
| True when at least one backend failed before another succeeded (or all failed). |
|
| Per-backend notes about filters that could not be applied verbatim (page size clamp, unsupported domains, etc.). |
|
| Structured per-backend errors: |
SearchResult fields: title, url, snippet, published_date, author, score, highlights, engine, provider_metadata.
Scores are not comparable across providers.
scoreis preserved provider-native andprovider_metadatacarries the type context (e.g. Exa similarity, SearXNG instance-local score). There is no cross-provider normalization.
Development
Running tests
uv sync --frozen --extra dev
.venv/bin/pytest -qOr in Docker (fully network-isolated):
docker buildx build --load --target test -f Dockerfile -t agent-tools:test .
docker run --rm --network=none agent-tools:testAll HTTP is mocked with respx; tests pass with --network=none.
Adding a new backend
Add
src/agent_tools/families/websearch/backends/<name>.pyimplementingBackendAdapter.search(key, request) -> BackendResult.Add a
normalize_<name>(raw)function innormalizer.pythat maps the raw payload toSearchResult(preserve every native field underprovider_metadata.<name>).Register the adapter and a
TokenPoolinfamilies/websearch/tool.py(WebSearchFamily.__init__).Add the backend name to
BackendNameinschemas.pyand to the chain validator inconfig.py.Add
tests/test_<name>_adapter.pyusingrespxto mock success / empty / rate-limit / auth / quota / 5xx paths.
Adding a new tool family
Tool families live under src/agent_tools/families/<name>/ and are self-contained: schemas, adapters (if any), and tool registration.
Create the family package.
mkdir -p src/agent_tools/families/<name>and add__init__.py.Define pydantic schemas. Create
schemas.pywith an input model and an output model. These drive the MCPinputSchema/outputSchemaautomatically.Implement the tool functions. Create a module (e.g.
tool.py) exposing one async callable per MCP tool, typed with the pydantic models from step 2. Group shared state (HTTP clients, pools) in a class.Register in
server.py. Construct the family increate_app()and attach its tools to the sharedFastMCPinstance, typically via aregister(mcp, family)hook.Add tests. Create
tests/test_<name>.pywith mocked external I/O. If the family calls HTTP services, mock them withrespxso the suite remains--network=noneclean.
The server shell (auth, JSONL logging, bind/mount, error envelope) does not change when a family is added.
Project layout
src/agent_tools/
__init__.py
__main__.py # python -m agent_tools entrypoint
server.py # FastMCP app construction
config.py # env + backends.yaml loading
logging.py # JSONL -> stderr
http_client.py # shared httpx.AsyncClient factory
pool.py # TokenPool: health-aware weighted round-robin
families/
websearch/
schemas.py # WebSearchInput / WebSearchOutput / SearchResult
fallback.py # cross-backend state machine
normalizer.py # per-provider canonical mapping
tool.py # FastMCP tool registration
backends/
base.py # BackendAdapter ABC + BackendResult + ErrorClass
brave.py
exa.py
searxng.py
tests/ # pytest + respx (all network mocked)
examples/backends.yaml # documented sample policy
Dockerfile # multi-stage: builder + runtime + test targetOperational notes
Bearer auth is optional. Loopback + tailnet is the default boundary; set
TOOLS_AUTH_ENABLED=trueandTOOLS_AUTH_TOKENfor defense-in-depth.Quarantine is sticky. A key that returns 401/403 is quarantined until an operator calls the pool's
clear_quarantine(v1: restart the server to clear all state). This prevents a known-bad credential from being retried on every request.Team suspension (Exa). 402
TEAM_BUDGET_EXCEEDEDsuspends the entire Exa pool for one hour by default (override via backends.yaml); individual key suspensions last until the process restarts.No caching in v1. Per-query caching and SearXNG-native cache control are deferred to a future release.
This server cannot be installed
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityDmaintenanceA lightweight MCP server that enables LLMs to search the web via DuckDuckGo, search GitHub code repositories, and extract clean content from web pages in LLM-friendly formats.Last updated8
- Alicense-qualityBmaintenanceA unified MCP server aggregating 15 web search and extraction tools across 5 providers (Jina, Tavily, Exa, Firecrawl, Bocha) with automatic API key validation and plugin architecture.Last updatedMIT
- Alicense-qualityAmaintenanceAggregates search results from multiple providers (web, academic, code, finance) with a unified JSON schema, providing both an HTTP API and an MCP server for AI agent tooling.Last updated52MIT
- Alicense-qualityAmaintenanceA minimal MCP server that exposes a private SearXNG instance as a search tool over streamable-HTTP, enabling web search from the llama.cpp WebUI or any compatible MCP client.Last updated1MIT
Related MCP Connectors
AI Reasoning Cache & Consensus Layer with 11 MCP tools via Streamable HTTP.
Multi-engine search for AI agents. Trust scoring, local corpus, MCP-native. Self-hostable, BYOK.
Local-first RAG engine with MCP server for AI agent integration.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/garnetlyx/agent-tools'
If you have feedback or need assistance with the MCP directory API, please join our Discord server