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.
Quick start: MCP over stdio
Section titled “Quick start: MCP over stdio”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:
uvx --from "tinysuite-search[server]" tinysearch setupCheck an existing installation without downloading anything:
uvx --from "tinysuite-search[server]" tinysearch doctorChoose an installation
Section titled “Choose an installation”| 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 |
Docker with SearXNG
Section titled “Docker with SearXNG”Run the published TinySearch image with its own SearXNG instance:
docker compose -f "https://github.com/TinySuiteHQ/TinySearch.git#main:compose.quickstart.yaml" up -dConnect 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 port8000.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:
docker compose -f "https://github.com/TinySuiteHQ/TinySearch.git#main:compose.quickstart.yaml" downPython library
Section titled “Python library”Install the core package:
pip install tinysuite-searchimport asynciofrom 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.
Run from source
Section titled “Run from source”Use source mode when you want to inspect or modify TinySearch:
git clone https://github.com/TinySuiteHQ/TinySearchcd TinySearch
python -m venv .venvsource .venv/bin/activatepip install -e ".[server]"tinysearch setupRun tinysearch or tinysearch mcp for stdio, and tinysearch serve for Streamable HTTP on port 8000.
MCP tools
Section titled “MCP tools”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.
get_current_datetime()
Section titled “get_current_datetime()”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>search(items)
Section titled “search(items)”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.
scrape_urls(items)
Section titled “scrape_urls(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.
Related links
Section titled “Related links”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 (
queryomitted 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 anotherscrape_urlscall. - Document scrapes (PDF, DOCX) do not produce related links;
related_linksis empty for those items.
browser_navigate and browser_act
Section titled “browser_navigate and browser_act”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.
What your agent gets
Section titled “What your agent gets”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.
Output formats
Section titled “Output formats”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_domainsand the built-in URL safety rules. - My searches, My pages (per caller): that caller’s own history in
historymode. Theclear_search_historytool, added only inhistorymode, 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_HEADERheader, believed only from peers inTINYSEARCH_TRUSTED_PROXY_CIDRSand checked on every message.
Management over HTTP with neither a token nor a trusted identity refuses to start.
OpenTelemetry
Section titled “OpenTelemetry”TinySearch exports vendor-neutral traces and metrics only when OpenTelemetry is configured. Add the telemetry extra and set the standard variables:
OTEL_SERVICE_NAME=tinysearch \OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318 \uvx --from "tinysuite-search[server,telemetry]" tinysearch serveThe 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.
Configuration
Section titled “Configuration”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)Docker override
Section titled “Docker override”Mount an explicit file when you want to customize the container:
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:latestThe 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.
MCP budgets
Section titled “MCP budgets”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.
Environment variables
Section titled “Environment variables”Config and models
Section titled “Config and models”| 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.
MCP server
Section titled “MCP server”| 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.
Activity and management
Section titled “Activity and management”| 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. |
FastAPI runtime config
Section titled “FastAPI runtime config”The optional FastAPI adapter exposes GET /config. PUT /config remains read-only unless both conditions are met:
TINYSEARCH_CONFIG_WRITABLE=1TINYSEARCH_CONFIG_PATH=/an/explicit/writable/tinysearch_config.jsonThis 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.
Search backend
Section titled “Search backend”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:
ddgsuses the DDGS package’s automatic backend selection.duckduckgoforces DDGS to use its DuckDuckGo backend.searxngqueriessearch_backend_url. When fallback is enabled, request failures and empty responses with SearXNGunresponsive_enginesfall back to DDGS; ifBRAVE_SEARCH_API_KEYis set, Brave is the final fallback when DDGS also fails or returns no results.autotries 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 automation
Section titled “Browser automation”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. |
External browser over CDP
Section titled “External browser over CDP”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.
Proxies
Section titled “Proxies”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.
Crawling and extraction
Section titled “Crawling and extraction”| 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. |
Chunk ranking
Section titled “Chunk ranking”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. |
Embeddings and token counting
Section titled “Embeddings and token counting”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.
Troubleshooting
Section titled “Troubleshooting”Check the installation
Section titled “Check the installation”Run the readiness check before debugging individual requests:
tinysearch doctorIt 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:
tinysearch setupOn Linux, include OS-level Chromium dependencies when needed:
tinysearch setup --with-system-depsServer dependencies are missing
Section titled “Server dependencies are missing”The Python library has a smaller core install. MCP, FastAPI, and Uvicorn are in the server extra:
pip install "tinysuite-search[server]"First scrape or browser call is slow
Section titled “First scrape or browser call is slow”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.
MCP client cannot connect
Section titled “MCP client cannot connect”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/mcpStreamable HTTP is served on /mcp; legacy SSE is served on /mcp/sse.
SearXNG returns HTML
Section titled “SearXNG returns HTML”TinySearch needs SearXNG JSON output. Add json to search.formats:
search: formats: - html - jsonThe quick-start Compose file already enables this.
Search backend behavior is unexpected
Section titled “Search backend behavior is unexpected”Native pip and uvx installs default to ddgs. The Docker quick start overrides the backend to searxng.
ddgslets the DDGS package select a backend.duckduckgopins DDGS to DuckDuckGo.searxnguses the configured SearXNG URL. Withsearch_backend_fallbackset totrue, TinySearch also treats an empty response that listsunresponsive_enginesas a backend outage, then falls back to DDGS.autotries 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.
Config changes are ignored
Section titled “Config changes are ignored”Server processes resolve configuration in this order:
- Built-in defaults.
- The JSON file selected by
TINYSEARCH_CONFIG_PATH, or the native per-user config location. - Supported environment overrides such as
TINYSEARCH_SEARCH_BACKEND,SEARXNG_URL,TINYSEARCH_EMBEDDING_BACKEND, andTINYSEARCH_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=...).
FastAPI config updates return 403
Section titled “FastAPI config updates return 403”PUT /config is disabled by default. To enable it, set both:
TINYSEARCH_CONFIG_WRITABLE=1TINYSEARCH_CONFIG_PATH=/an/explicit/writable/tinysearch_config.jsonDo 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.
Dense weight errors
Section titled “Dense weight errors”Chunk ranking requires dense embeddings. Keep chunk_dense_weight greater than zero:
{ "chunk_dense_weight": 0.5}URLs are rejected before crawling
Section titled “URLs are rejected before crawling”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.