Skip to content

TinySearch

TinySearch is a self-hosted web-retrieval layer for AI agents. Use fast, backend-ordered search to find pages, then inspect only the URLs worth putting in your model’s context.

TinySearch does not make another model call to write the final answer. MCP tools return XML-shaped context for your client model; the Python and FastAPI APIs return stable, JSON-serializable results for applications that need to inspect or transform them.

For a complete chat-client walkthrough, follow Add web search to Open WebUI. To choose and configure a search backend, see SearXNG vs DuckDuckGo and DDGS.

With uv installed, add TinySearch to your MCP client:

{
"mcpServers": {
"tinysearch": {
"command": "uvx",
"args": [
"--python",
"3.12",
"--from",
"tinysuite-search[server]",
"tinysearch"
]
}
}
}

The client starts TinySearch over stdio when it needs it. This path uses DDGS for search and does not require a repository clone, SearXNG instance, hosted account, or paid search key.

Fast search does not initialize Chromium or an embedding model. The first scrape or browser call initializes Chromium; focused scraping also initializes the configured local embedding model. Pre-warm both before those workflows if you want to avoid the first-use delay:

Terminal window
uvx --from "tinysuite-search[server]" tinysearch setup

Check an existing installation without downloading anything:

Terminal window
uvx --from "tinysuite-search[server]" tinysearch doctor
Use case Entry point Default search backend
Python application pip install tinysuite-search DDGS
MCP client launches the server uvx --from "tinysuite-search[server]" tinysearch DDGS
Full self-hosted HTTP stack Docker Compose Bundled SearXNG

Run the published TinySearch image with its own SearXNG instance:

Terminal window
docker compose -f "https://github.com/TinySuiteHQ/TinySearch.git#main:compose.quickstart.yaml" up -d

Connect your MCP client to the Streamable HTTP endpoint:

{
"mcpServers": {
"tinysearch": {
"url": "http://localhost:8000/mcp"
}
}
}

The quick-start stack includes:

  • tinysearch: the MCP server on port 8000.
  • searxng: local web search with JSON output enabled.
  • models: persistent storage for downloaded local embedding models.

Legacy SSE is available at http://localhost:8000/mcp/sse.

Stop the stack later:

Terminal window
docker compose -f "https://github.com/TinySuiteHQ/TinySearch.git#main:compose.quickstart.yaml" down

Install the core package:

Terminal window
pip install tinysuite-search
import asyncio
from tinysearch import scrape_urls, search
async def main():
results = await search([{"query": "How does asyncio cancellation work?"}])
page_url = results["items"][0]["results"][0]["url"]
evidence = await scrape_urls([{
"url": page_url,
"query": "How does asyncio cancellation work?",
}])
print(evidence["results"])
asyncio.run(main())

search() and scrape_urls() return schema-v1 dictionaries. Programmatic calls accept a TinySearchConfig or a partial config mapping for per-call overrides and do not read server environment configuration.

Use source mode when you want to inspect or modify TinySearch:

Terminal window
git clone https://github.com/TinySuiteHQ/TinySearch
cd TinySearch
python -m venv .venv
source .venv/bin/activate
pip install -e ".[server]"
tinysearch setup

Run tinysearch or tinysearch mcp for stdio, and tinysearch serve for Streamable HTTP on port 8000.

TinySearch exposes five focused MCP tools. They return context for the calling model; they do not make another model call or write the final answer. A sixth, clear_search_history, appears only when activity history is on. The recommended flow is search followed by scrape_urls, with the browser_* tools reserved for pages that need interaction.

Returns the current date and time in UTC.

Call it before research involving “latest,” “today,” “last month,” or another relative date, and before adding date context to a query.

The result is XML-shaped context containing the current UTC date and time.

<current_datetime>
<date_utc>2026-08-08</date_utc>
<time_utc>10:30:00</time_utc>
</current_datetime>

Use search(items) for fast top-level discovery. It returns backend-ordered titles, URLs, previews, and upstream dates when available. It does not crawl pages or rerank results.

Parameter Type Description
items array One to five items, each with a query and optional positive domains restriction. Use one item for a simple lookup; add more only for independent subquestions.

Example:

search(items=[{"query": "What changed in the latest TinySearch release?"}])

Restrict an item to specific domains:

search(items=[{"query": "Form 8-K Tesla", "domains": ["sec.gov"]}])

Search results are returned in an XML <search_results> block, one section per item. search_max_results in the server configuration sets the per-item MCP result count; it is not a tool parameter. A single deprecated query (with optional domains) is still accepted for backward compatibility, but new integrations should use items.

Use scrape_urls(items) when one or more URLs are already known. It accepts one to five items, each with a required url and optional query.

Item field Type Description
url string The exact public HTTP or HTTPS URL supplied by the user or found through search.
query string, optional Omit it or use "*" for clean page-order Markdown. Provide a focused query to chunk and hybrid-rank that page.

This is the right tool when:

  • search() identified one or more pages to inspect.
  • The user pasted one or more URLs.
  • You need focused evidence from a known page.

Example:

scrape_urls([{
"url": "https://github.com/TinySuiteHQ/TinySearch",
"query": "What MCP tools does it expose?"
}])

For page-order mode, omit the query or use "*":

scrape_urls([{"url": "https://example.com", "query": "*"}])

Each item returns independently, so one failed URL does not discard successful items. scrape_max_tokens in the server configuration sets each MCP item’s content budget; it is not a tool parameter. Repeat a URL in separate items when it needs distinct focused queries — the five-item cap bounds total work per call.

Scraping an HTML page also returns up to scrape_max_links bounded candidate links (default 8) found on that page, each with its URL and label:

<related_links>
<link rank="1">
<url>https://example.com/getting-started</url>
<text>Getting started guide</text>
</link>
</related_links>
  • With a focused query, candidates are ranked with the same hybrid BM25 + embedding retrieval used for content chunks.
  • In page-order mode (query omitted or "*"), candidates keep their order on the page and are not scored.
  • TinySearch never follows these links itself. Inspect related_links, then pass any URL worth pursuing to another scrape_urls call.
  • Document scrapes (PDF, DOCX) do not produce related links; related_links is empty for those items.

scrape_urls is a static fetch. It cannot see content that JavaScript renders after load, content behind a cookie interstitial, a “load more” control, or a client-side search UI. For those pages, TinySearch drives a real browser through the same Playwright/Chromium install it already uses for scraping — no second runtime, no child process.

Call scrape_urls on the exact URL first. Reach for the browser tools only when that call comes back thin, empty, or clearly incomplete, or when the page needs a read-only interaction before it will reveal what was asked for.

browser_navigate(url, find?, depth?) opens an exact URL in a live browser and returns a compact accessibility-tree view of the page.

Parameter Type Description
url string Exact URL to open.
find string, optional Regex (case-insensitive), falling back to a literal substring match, narrowing the returned view to matching nodes and their context instead of the whole tree.
depth integer, optional Unfiltered view only: maximum tree depth. 0 uses the configured default.

browser_act(action, ...) runs one action on the page browser_navigate opened, dispatched by action:

Action Arguments Description
look find?, depth? Read the current page without touching it.
click target, find?, depth? Click the element with ref target (e.g. "e42"), seen in a prior view.
type target, text, submit?, find?, depth? Type text into ref target; submit presses Enter afterward.
wait_for time or text or text_gone Wait for a duration in seconds, for text to appear, or for text to disappear.
tabs tab_action (list|new|select|close), index? Manage browser tabs.
close — Close the page session.

Every action except close returns a page view, so pass find to keep the response small — a full page view can be 50x larger than a filtered one.

browser_navigate(url="...", find="Accept") -> "- button \"Accept all\" [ref=e79]"
browser_act(action="click", target="e79", find="Results")

click and type accept only a ref the model actually observed in a returned view — TinySearch never invents a selector or synthesizes a DOM event. There is no tool that executes arbitrary code, fills forms blindly, uploads files, or drags elements: the capability is absent, not merely discouraged.

Each MCP client connection gets its own browser and page, so clients sharing one server never see each other’s tabs or cookies. At most browser_max_sessions (default 4) are open at once. A new caller waits up to browser_session_wait_seconds (default 30) for one to close, either explicitly or after its idle timeout, and then gets an error. The FastAPI /browser/* endpoints share one browser.

Set browser_storage_state_path so a consent banner accepted once persists across later navigations. No browser profile lock is taken, so concurrent clients don’t conflict. Set "browser_backend": "off" to remove both tools from the tool list entirely rather than have them refuse to run.

TinySearch does not spend another model call writing the final answer. The recommended flow is search for lightweight discovery, then scrape_urls for the pages worth reading, with browser_navigate/browser_act as a fallback for interactive pages.

MCP still uses its standard JSON-RPC transport envelope, including protocol-level errors and optional structuredContent. Python and FastAPI keep their structured JSON contracts for applications that need to store, inspect, or transform the evidence.

The MCP tools intentionally expose prompt-shaped responses only. They do not accept an output_format parameter.

Use the Python API when you need structured schema-v1 results or per-call limits:

from tinysearch import scrape_urls, search
results = await search([{"query": "How does asyncio cancellation work?"}])
evidence = await scrape_urls(
[{"url": results["items"][0]["results"][0]["url"]}],
max_tokens=4000,
)

The optional FastAPI adapter’s /search and /scrape endpoints return structured per-item outcomes; /browser/navigate and /browser/act return {"result": "..."} with the same accessibility view as the MCP tools.

Activity and management views

TinySearch records nothing about what it searched unless you turn recording on with TINYSEARCH_ACTIVITY:

Mode Kept
off (default) Nothing.
aggregate Per operation: outcome, latency and error code. Per domain: how often it appeared in search results (discovered) or was fetched, and how often fetching it failed. No queries, URLs or callers.
history Aggregate, plus each caller’s own queries and URLs, readable and deletable only by that caller. Activity from a caller without a trusted identity stays aggregate.

Records older than TINYSEARCH_ACTIVITY_RETENTION_DAYS (default 7) are deleted, and only the newest TINYSEARCH_ACTIVITY_MAX_EVENTS (default 5000) are kept. Callers are stored as salted hashes. Everything lives in one SQLite file, TINYSEARCH_ACTIVITY_DB (default: activity.db in the per-user data directory).

TINYSEARCH_MANAGEMENT=on exposes these records to a client UI, such as TinyWebUI’s Integrations panel, as MCP resources that follow the tinysuite.manage/1 contract (manifest tinysearch://manage/manifest, media type application/vnd.tinysuite.manage+json):

  • Overview, Domains, Blocking rules (instance-wide): health and counts, discovered versus fetched domains, the configured blocked_domains and the built-in URL safety rules.
  • My searches, My pages (per caller): that caller’s own history in history mode. The clear_search_history tool, added only in history mode, deletes it.

Who may read what:

  • A local stdio client is the operator and sees everything.
  • Over HTTP, the instance-wide views need Authorization: Bearer <TINYSEARCH_MANAGEMENT_TOKEN> (at least 24 characters).
  • Over HTTP, a caller’s own views need a trusted identity: the TINYSEARCH_TRUSTED_USER_HEADER header, believed only from peers in TINYSEARCH_TRUSTED_PROXY_CIDRS and checked on every message.

Management over HTTP with neither a token nor a trusted identity refuses to start.

TinySearch exports vendor-neutral traces and metrics only when OpenTelemetry is configured. Add the telemetry extra and set the standard variables:

Terminal window
OTEL_SERVICE_NAME=tinysearch \
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318 \
uvx --from "tinysuite-search[server,telemetry]" tinysearch serve

The Docker image already includes the extra. http/protobuf is the default protocol and grpc is supported; OTEL_SDK_DISABLED=true, OTEL_TRACES_EXPORTER=none and OTEL_METRICS_EXPORTER=none turn telemetry off. Treat OTEL_EXPORTER_OTLP_HEADERS as a secret.

TinySearch exports timings, outcomes, counts, backend state, browser use, token counts and embedding model metadata. It never exports queries, URLs, domains, prompts, documents, snippets, request headers, credentials, configuration paths, raw errors, stack traces, or MCP arguments and results. Python library users configure their own provider; TinySearch only sets one up for its MCP and FastAPI servers.

TinySearch accepts a flat JSON configuration object. The repository includes an annotated Docker-oriented example at configs/tinysearch_config.json.

Server processes load TINYSEARCH_CONFIG_PATH when it is set. A native pip or uvx install otherwise uses TinySearch’s OS-specific per-user config directory. If no file exists, built-in defaults apply.

The Python API does not read server files or environment variables. Pass a partial mapping or TinySearchConfig directly:

from tinysearch import TinySearchConfig, search
config = TinySearchConfig(
search_backend="ddgs",
search_max_results=6,
)
results = await search([{"query": "What is reciprocal rank fusion?"}], config=config)

Mount an explicit file when you want to customize the container:

Terminal window
docker run --rm \
-p 8000:8000 \
-v tinysearch-models:/data/models \
-v "$PWD/configs/tinysearch_config.json:/config/tinysearch_config.json:ro" \
-e TINYSEARCH_CONFIG_PATH=/config/tinysearch_config.json \
-e MCP_TRANSPORT=streamable-http \
-e MCP_HOST=0.0.0.0 \
marcellm01/tinysearch:latest

The bundled compose.quickstart.yaml uses built-in config defaults but overrides the search backend to its SearXNG service. The repository’s full compose.yaml mounts configs/tinysearch_config.json, whose broader limits intentionally differ from the built-in defaults below.

The MCP interface keeps request parameters intentionally small. Set its limits in JSON configuration instead of asking client models to choose them:

Setting Built-in default Use
search_max_results 10 Number of backend-ordered results returned per search item.
search_max_concurrent_items 3 Maximum search items resolved concurrently in one batch call.
scrape_max_tokens 2000 Maximum content tokens returned for each MCP scrape_urls item.
scrape_max_links 8 Maximum related-link candidates returned for each MCP scrape_urls item.
scrape_max_link_tokens 500 Token budget for the related-links block per scrape_urls item.

Python and FastAPI remain separate: Python search() and scrape_urls() accept per-call overrides through config, and FastAPI exposes equivalent request fields.

Variable Use
TINYSEARCH_CONFIG_PATH Load an explicit JSON config file.
TINYSEARCH_SEARCH_BACKEND Override search_backend.
SEARXNG_URL Override search_backend_url.
TINYSEARCH_EMBEDDING_BACKEND Override embedding_backend.
TINYSEARCH_EMBEDDING_MODEL Override embedding_model.
TINYSEARCH_MODELS_DIR Move the local ONNX model cache.
TINYSEARCH_ONNX_MODEL_DIR Point at one exact local ONNX bundle directory.
BRAVE_SEARCH_API_KEY Enable Brave as a keyed fallback when DDGS errors or returns no results.
TINYSEARCH_BROWSER_BACKEND Override browser_backend. Operator-managed: startup-only, not writable over HTTP.
TINYSEARCH_BROWSER_CDP_URL Override browser_cdp_url. Connect through an external browser’s CDP endpoint instead of the bundled Chromium. Operator-managed.
TINYSEARCH_BROWSER_STORAGE_STATE_PATH Override browser_storage_state_path. Operator-managed.
TINYSEARCH_BROWSER_PROXY_SERVER, TINYSEARCH_BROWSER_PROXY_USERNAME, TINYSEARCH_BROWSER_PROXY_PASSWORD, TINYSEARCH_BROWSER_PROXY_BYPASS Override the proxy settings. Operator-managed.

Environment values listed above override values from the JSON file.

Variable Use
MCP_TRANSPORT stdio, sse, or streamable-http. The CLI defaults to stdio; tinysearch serve selects Streamable HTTP.
MCP_HOST / MCP_PORT Bind host and port for SSE or Streamable HTTP. Defaults: 127.0.0.1:8000.
MCP_CORS_ORIGINS Comma-separated allowed origins for browser MCP clients. Defaults to *.
TINYSEARCH_DUMP_TRACEBACK_AFTER Repeatedly dump server thread tracebacks after this many seconds for hang diagnosis.

Transport belongs in the process environment, not the JSON config.

Variable Use
TINYSEARCH_ACTIVITY off (default), aggregate or history. See Activity and management views.
TINYSEARCH_ACTIVITY_DB SQLite file for activity records.
TINYSEARCH_ACTIVITY_RETENTION_DAYS Delete records older than this. Default 7.
TINYSEARCH_ACTIVITY_MAX_EVENTS Keep only this many newest records. Default 5000.
TINYSEARCH_MANAGEMENT on to expose the management views. Default off.
TINYSEARCH_MANAGEMENT_TOKEN Bearer token (24+ characters) for the instance-wide views over HTTP.
TINYSEARCH_TRUSTED_USER_HEADER Header that carries a verified caller identity from a trusted proxy.
TINYSEARCH_TRUSTED_PROXY_CIDRS Peers whose identity header is believed.

The optional FastAPI adapter exposes GET /config. PUT /config remains read-only unless both conditions are met:

TINYSEARCH_CONFIG_WRITABLE=1
TINYSEARCH_CONFIG_PATH=/an/explicit/writable/tinysearch_config.json

This prevents accidental writes to implicit or read-only configuration. browser_backend, browser_cdp_url, browser_storage_state_path and the four browser_proxy_* settings are always rejected by PUT /config, even when writes are enabled — they are operator-managed, set only via environment variable or the config file, then applied on restart.

The native default is ddgs; Docker’s quick start overrides it to searxng.

Setting Built-in default Use
search_backend ddgs ddgs, searxng, duckduckgo, or auto.
search_backend_url http://searxng:8080/search SearXNG JSON endpoint.
search_backend_fallback true For searxng, fall back when SearXNG errors or reports every engine unresponsive.
search_engines [] Optional SearXNG engines, for example ["google", "bing"].
search_region "" Optional backend language or region, for example us-en.
ddgs_backend auto DDGS backend selection. Pin only when necessary.
ddgs_timeout_seconds 20.0 Timeout for one DDGS request.
searxng_timeout_seconds 8.0 Timeout for one SearXNG request.
blocked_domains [] Domains excluded from search and crawl. Subdomains are blocked too.
search_top_k 10 Raw web results requested before ranking.

Backend behavior:

  • ddgs uses the DDGS package’s automatic backend selection.
  • duckduckgo forces DDGS to use its DuckDuckGo backend.
  • searxng queries search_backend_url. When fallback is enabled, request failures and empty responses with SearXNG unresponsive_engines fall back to DDGS; if BRAVE_SEARCH_API_KEY is set, Brave is the final fallback when DDGS also fails or returns no results.
  • auto tries SearXNG and then DDGS.

For ddgs and duckduckgo, setting BRAVE_SEARCH_API_KEY adds Brave’s official Web Search API as a fallback only when the primary request errors or returns no results.

Minimal SearXNG config:

{
"search_backend": "searxng",
"search_backend_url": "http://searxng:8080/search",
"search_engines": ["google", "bing"],
"search_region": "us-en",
"search_backend_fallback": true
}

Block domains:

{
"blocked_domains": ["example.com", "spammy-site.test"]
}

Entries match the domain and its subdomains. URL-style entries such as https://example.com/path are normalized to the hostname.

browser_navigate and browser_act share the same Chromium install as scrape_urls.

Setting Built-in default Use
browser_backend playwright playwright or off. off removes both browser tools from the tool list. Operator-managed.
browser_cdp_url "" Connect through an external browser’s Chrome DevTools Protocol endpoint instead of launching bundled Chromium. Operator-managed.
browser_storage_state_path "" Server-side path for persisted cookies/storage state across navigations, e.g. so a cookie banner is accepted once. Operator-managed.
browser_snapshot_depth 0 Default accessibility-tree depth when a call doesn’t pass depth. 0 uses the built-in default.
browser_response_char_budget 20000 Maximum characters returned in one browser tool response.
browser_idle_shutdown_seconds 300.0 Idle time before a browser session is closed.
browser_action_timeout_seconds 10.0 Timeout for one browser action (click, type, wait_for, etc).
browser_max_sessions 4 Maximum interactive browsers open at once, one per MCP client connection.
browser_session_wait_seconds 30.0 How long a new caller waits for a free browser before its call fails. 0 refuses at once.
browser_proxy_server "" Proxy for crawling and the browser tools. See Proxies. Operator-managed.
browser_proxy_username / browser_proxy_password "" Proxy credentials. Operator-managed.
browser_proxy_bypass "" Comma-separated hosts that skip the proxy. Operator-managed.

TinySearch uses its bundled Playwright Chromium by default. To use a browser you operate separately, set its Chrome DevTools Protocol endpoint in the config file:

{
"browser_cdp_url": "http://browser:9222"
}

Server processes also accept TINYSEARCH_BROWSER_CDP_URL. When either setting is present, TinySearch connects through that endpoint for both the scrape pipeline and the browser tools instead of installing or launching the bundled Chromium. The external browser owns its executable, profile, proxy, and fingerprint configuration; TinySearch does not select or install a particular browser backend.

Treat a CDP endpoint as privileged remote control of the browser. Keep it on a private network or loopback interface, require authentication when it crosses a host boundary, and do not expose port 9222 directly to the public internet. When TinySearch itself runs in Docker, localhost refers to the TinySearch container, so use an endpoint reachable from that container.

The CDP endpoint is operator-managed and cannot be changed through the HTTP PUT /config endpoint, even when configuration writes are enabled. Set it in the startup environment or the file selected by TINYSEARCH_CONFIG_PATH, then restart TinySearch. HTTP clients can continue updating other settings by omitting browser_cdp_url from their partial update.

To route crawling through a proxy, set browser_proxy_server:

{
"browser_proxy_server": "http://gateway.proxyprovider.com:8000",
"browser_proxy_username": "your-username",
"browser_proxy_password": "your-password"
}

http, https and socks5 proxy URLs are supported, matching what Playwright accepts. If your provider hands out several fixed gateway endpoints, separate them with commas and TinySearch uses them in turn, one per new browser context. Use browser_proxy_bypass for a comma-separated list of hosts that should skip the proxy. Like the CDP settings, these four are operator-managed: set them in the config file or with TINYSEARCH_BROWSER_PROXY_SERVER, TINYSEARCH_BROWSER_PROXY_USERNAME, TINYSEARCH_BROWSER_PROXY_PASSWORD and TINYSEARCH_BROWSER_PROXY_BYPASS, never over HTTP.

Setting Built-in default Use
pipeline_timeout_seconds 120.0 Overall scrape timeout. Use null to disable it.
crawl_max_page_tokens 0 Maximum retained page tokens before chunking. Zero leaves the page uncapped.
crawl_max_chunk_tokens 300 Target token size for candidate chunks.
crawl_overlap_tokens 80 Token overlap between adjacent chunks.

Applies when a scrape_urls item’s query is a focused query rather than page-order mode.

Setting Built-in default Use
chunk_dense_weight 0.5 Dense embedding share in chunk fusion. Must be greater than zero.
chunk_rrf_cutoff 0.0 Minimum fused chunk score. Zero disables score filtering.

TinySearch requires dense embeddings. The default backend is local ONNX.

Setting Built-in default Use
embedding_backend onnx Local ONNX or openai_compatible.
embedding_model fast fast, balanced, quality, or a custom Hugging Face ONNX repo id.
embedding_openai_env_file .env Credentials and model file for openai_compatible.
max_concurrent_embedding_calls 3 Maximum concurrent embedding batches.
embedding_timeout_seconds 60.0 Timeout for an embedding call or batch.
embedding_timeout_retries 2 Retries after an embedding timeout.
dense_query_prefix non-empty retrieval instruction Optional text prepended before embedding the query.
dense_document_prefix "" Optional text prepended before embedding results and chunks.
dense_document_embed_batch_size 32 Documents or chunks sent to the embedder per batch.
encoding_name o200k_base Tokenizer for budgets. Use embedding to follow the selected embedding model.

Built-in local model presets:

Preset Hugging Face repository
fast onnx-models/all-MiniLM-L6-v2-onnx
balanced BAAI/bge-small-en-v1.5
quality BAAI/bge-base-en-v1.5

Pre-download Chromium and the selected ONNX model with tinysearch setup. If embedding_backend is openai_compatible, setup skips the local model download.

OpenAI-compatible config:

{
"embedding_backend": "openai_compatible",
"embedding_openai_env_file": ".env"
}
OPENAI_BASE_URL=
OPENAI_API_KEY=
OPENAI_EMBEDDING_MODEL=

OPENAI_BASE_URL is optional for api.openai.com. API_KEY, EMBEDDING_MODEL, and MODEL_NAME are accepted aliases for the corresponding values.

Run the readiness check before debugging individual requests:

Terminal window
tinysearch doctor

It verifies the resolved config location, Chromium installation, local ONNX model bundle, and writable config directory without downloading anything.

Install or repair the browser and configured local model with:

Terminal window
tinysearch setup

On Linux, include OS-level Chromium dependencies when needed:

Terminal window
tinysearch setup --with-system-deps

The Python library has a smaller core install. MCP, FastAPI, and Uvicorn are in the server extra:

Terminal window
pip install "tinysuite-search[server]"

Fast search begins without Chromium or a local embedding model. The first scrape or browser_navigate call initializes Chromium; a focused scrape also initializes the configured ONNX model. That first use can therefore take a minute or two.

Run tinysearch setup before starting the client. In Docker, keep a volume mounted at /data/models so the model cache survives restarts.

For a client-launched stdio server, run tinysearch doctor using the same Python or uvx environment as the client.

For Docker or tinysearch serve, verify the server is running and use:

http://localhost:8000/mcp

Streamable HTTP is served on /mcp; legacy SSE is served on /mcp/sse.

TinySearch needs SearXNG JSON output. Add json to search.formats:

search:
formats:
- html
- json

The quick-start Compose file already enables this.

Native pip and uvx installs default to ddgs. The Docker quick start overrides the backend to searxng.

  • ddgs lets the DDGS package select a backend.
  • duckduckgo pins DDGS to DuckDuckGo.
  • searxng uses the configured SearXNG URL. With search_backend_fallback set to true, TinySearch also treats an empty response that lists unresponsive_engines as a backend outage, then falls back to DDGS.
  • auto tries SearXNG and then DDGS.

Check TINYSEARCH_SEARCH_BACKEND, SEARXNG_URL, and your config file if the selected backend is not what you expected.

If BRAVE_SEARCH_API_KEY is set, Brave’s official API is used only after the DDGS or DuckDuckGo attempt errors or returns no results. That includes the fallback path after a SearXNG outage. A genuine empty SearXNG result without unresponsive_engines remains an empty result; it does not trigger a fallback.

Browser tools are unavailable or refuse to run

Section titled “Browser tools are unavailable or refuse to run”

If browser_navigate and browser_act don’t appear in the tool list, browser_backend is set to "off" in config or TINYSEARCH_BROWSER_BACKEND. Set it to "playwright" (or unset the override) and restart.

If a click or type call fails, the target ref is likely stale — it belongs to a view from before the last navigation or action. Call look (or reread the returned view) to get current refs before acting again.

Server processes resolve configuration in this order:

  1. Built-in defaults.
  2. The JSON file selected by TINYSEARCH_CONFIG_PATH, or the native per-user config location.
  3. Supported environment overrides such as TINYSEARCH_SEARCH_BACKEND, SEARXNG_URL, TINYSEARCH_EMBEDDING_BACKEND, and TINYSEARCH_EMBEDDING_MODEL.

The public Python API is deliberately isolated from ambient server config. Pass a mapping or TinySearchConfig to search(..., config=...) or scrape_urls(..., config=...).

PUT /config is disabled by default. To enable it, set both:

TINYSEARCH_CONFIG_WRITABLE=1
TINYSEARCH_CONFIG_PATH=/an/explicit/writable/tinysearch_config.json

Do not enable runtime writes against a read-only Compose mount. Requests that include browser_backend, browser_cdp_url, browser_storage_state_path or a browser_proxy_* setting are rejected regardless — those fields are operator-managed and require an environment variable or config-file change plus a restart.

Chunk ranking requires dense embeddings. Keep chunk_dense_weight greater than zero:

{
"chunk_dense_weight": 0.5
}

TinySearch rejects:

  • non-HTTP schemes
  • URLs with embedded credentials
  • private, loopback, link-local, multicast, reserved, or unspecified IP addresses
  • domains listed in blocked_domains

The safety check applies to the initial URL and the final URL reported after redirects.