Tavily Hikari is a Rust + Axum proxy for Tavily's MCP endpoint. It multiplexes multiple API keys, anonymizes upstream traffic, stores full audit logs in SQLite, and ships with a React + Vite web console for realtime visibility.
Looking for the Chinese documentation? Check
README.zh-CN.md.
- Key pool with fairness – SQLite keeps last-used timestamps and assigns each access token a short‑lived “home” key; new or expired affinities are resolved via least‑recently‑used selection across active keys to keep wear balanced.
- Short IDs and secret isolation – every Tavily key receives a 4-char nanoid. The real token is only retrievable via admin APIs/UI.
- Health-aware routing – status code 432 automatically marks keys as
exhausteduntil the next UTC month or manual recovery. - High-anonymity forwarding – only
/mcptraffic is tunneled upstream; sensitive headers are stripped or rewritten. Seedocs/high-anonymity-proxy.md. - Full audit trail –
request_logspersists method/path/query, upstream responses, error payloads, and the list of forwarded/dropped headers. - Operator UI – the SPA in
web/visualizes key health, request logs, and admin actions (soft delete, restore, reveal real keys). - CI + Release – GitHub Actions runs lint/tests; releases are driven by PR intent labels and publish
ghcr.io/ivanli-cn/tavily-hikari:<tag>with prebuilt web assets.
Client → Tavily Hikari (Axum) ──┬─> Tavily upstream (/mcp)
├─> SQLite (api_keys, request_logs)
└─> Web SPA (React/Vite)
- Backend: Rust 2024 edition, Axum, SQLx, Tokio, Clap.
- Data: SQLite single-file DB with
api_keys+request_logs. - Frontend: React 18, TanStack Router, Tailwind CSS, shadcn/ui (Radix), Vite 5 (served from
web/distor via Vite dev server).
# Start backend (high port recommended during dev)
cargo run -- --bind 127.0.0.1 --port 58087
# Optional: start SPA dev server
cd web && bun install --frozen-lockfile && bun run dev -- --host 127.0.0.1 --port 55173
# Register Tavily keys via admin API (ForwardAuth headers depend on your setup)
curl -X POST http://127.0.0.1:58087/api/keys \
-H "X-Forwarded-User: admin@example.com" \
-H "X-Forwarded-Admin: true" \
-H "Content-Type: application/json" \
-d '{"api_key":"key_a"}'Visit http://127.0.0.1:58087/health for a health check or http://127.0.0.1:55173 for the console. Keys should be managed via the admin API or SPA instead of environment variables.
docker run --rm \
-p 8787:8787 \
-v $(pwd)/data:/srv/app/data \
ghcr.io/ivanli-cn/tavily-hikari:latestThe container listens on 0.0.0.0:8787, serves web/dist, and persists data in /srv/app/data/tavily_proxy.db. Once it is up, register keys via the admin API/console.
docker compose up -d
# Seed initial keys (requires ForwardAuth headers)
curl -X POST http://127.0.0.1:8787/api/keys \
-H "X-Forwarded-User: admin@example.com" \
-H "X-Forwarded-Admin: true" \
-H "Content-Type: application/json" \
-d '{"api_key":"key_a"}'The stock docker-compose.yml exposes port 8787 and mounts a tavily-hikari-data volume. Override any CLI flag with additional environment variables if needed.
| Flag / Env | Description |
|---|---|
--keys / TAVILY_API_KEYS |
Optional helper for bootstrapping or local experiments. In production, prefer the admin API/UI to manage keys. |
--upstream / TAVILY_UPSTREAM |
Tavily MCP upstream (default https://mcp.tavily.com/mcp). |
--bind / PROXY_BIND |
Listen address (default 127.0.0.1). |
--port / PROXY_PORT |
Listen port (default 8787). |
--db-path / PROXY_DB_PATH |
SQLite file path (default tavily_proxy.db). |
--static-dir / WEB_STATIC_DIR |
Directory for static assets; auto-detected if web/dist exists. |
--forward-auth-header / FORWARD_AUTH_HEADER |
Request header that carries the authenticated user identity (e.g., Remote-Email). |
--forward-auth-admin-value / FORWARD_AUTH_ADMIN_VALUE |
Header value that grants admin privileges; leave empty to disable. |
--forward-auth-nickname-header / FORWARD_AUTH_NICKNAME_HEADER |
Optional header for displaying a friendly name in the UI (e.g., Remote-Name). |
--admin-mode-name / ADMIN_MODE_NAME |
Override nickname when ForwardAuth headers are missing. |
--admin-auth-forward-enabled / ADMIN_AUTH_FORWARD_ENABLED |
Boolean switch to enable ForwardAuth checks (default true). |
--admin-auth-builtin-enabled / ADMIN_AUTH_BUILTIN_ENABLED |
Boolean switch to enable built-in admin login (cookie session) (default false). |
--admin-auth-builtin-password-hash / ADMIN_AUTH_BUILTIN_PASSWORD_HASH |
Built-in admin password hash (PHC string, recommended). |
--admin-auth-builtin-password / ADMIN_AUTH_BUILTIN_PASSWORD |
Built-in admin password (deprecated; prefer password hash). |
--dev-open-admin / DEV_OPEN_ADMIN |
Boolean flag to bypass admin checks in local/dev setups (default false). |
--linuxdo-oauth-enabled / LINUXDO_OAUTH_ENABLED |
Enable Linux DO Connect OAuth2 login for end users (default false). |
--linuxdo-oauth-client-id / LINUXDO_OAUTH_CLIENT_ID |
Linux DO OAuth2 client ID (connect.linux.do app). |
--linuxdo-oauth-client-secret / LINUXDO_OAUTH_CLIENT_SECRET |
Linux DO OAuth2 client secret. |
--linuxdo-oauth-authorize-url / LINUXDO_OAUTH_AUTHORIZE_URL |
OAuth2 authorize endpoint (default https://connect.linux.do/oauth2/authorize). |
--linuxdo-oauth-token-url / LINUXDO_OAUTH_TOKEN_URL |
OAuth2 token endpoint (default https://connect.linux.do/oauth2/token). |
--linuxdo-oauth-userinfo-url / LINUXDO_OAUTH_USERINFO_URL |
OAuth2 user profile endpoint (default https://connect.linux.do/api/user). |
--linuxdo-oauth-scope / LINUXDO_OAUTH_SCOPE |
OAuth scope (default user). |
--linuxdo-oauth-redirect-url / LINUXDO_OAUTH_REDIRECT_URL |
Callback URL on this service (for example https://tavily.ivanli.cc/auth/linuxdo/callback). |
--user-session-max-age-secs / USER_SESSION_MAX_AGE_SECS |
End-user login cookie max age in seconds (default 1209600, 14 days). |
--oauth-login-state-ttl-secs / OAUTH_LOGIN_STATE_TTL_SECS |
One-time OAuth state token TTL in seconds (default 600). |
If --keys/TAVILY_API_KEYS is supplied, the database sync logic adds or revives keys listed there and soft deletes the rest. Otherwise, the admin workflow fully controls key state.
| Method | Path | Description | Auth |
|---|---|---|---|
GET |
/health |
Liveness probe. | none |
GET |
/api/summary |
High-level success/failure stats and last activity. | none |
GET |
/api/keys |
Lists short IDs, status, and counters. | Admin |
GET |
/api/logs?page=1 |
Recent proxy logs (paginated, default 20 per page). | Admin |
POST |
/api/tavily/search |
Tavily /search proxy via Hikari key pool (Cherry Studio, etc.). |
Hikari token |
POST |
/api/keys |
Admin: add/restore a key. Body { "api_key": "..." }. |
Admin |
DELETE |
/api/keys/:id |
Admin: soft-delete key by short ID. | Admin |
GET |
/api/keys/:id/secret |
Admin: reveal the real Tavily key. | Admin |
Tavily Hikari also exposes a Tavily HTTP façade so Cherry Studio and other HTTP clients can talk to Tavily through Hikari’s key pool and per-token quotas instead of calling Tavily directly.
- Base URL:
https://<your Hikari host>/api/tavily - API key: Hikari access token
th-<id>-<secret>created from the user dashboard
Cherry Studio setup:
- Create an access token (for example
th-xxxx-xxxxxxxxxxxx) from the Tavily Hikari user dashboard and copy it. - In Cherry Studio, open Settings → Web Search.
- Choose the provider Tavily (API key).
- Set API URL to
https://<your Hikari host>/api/tavily(for local dev it is usuallyhttp://127.0.0.1:58087/api/tavily). - Set API key to the Hikari access token from step 1 (the full
th-xxxx-xxxxxxxxxxxxvalue), not your Tavily official API key. - Optionally tune result count, answer/date options, etc.; Cherry Studio will pass these fields through to Tavily while Hikari rotates Tavily keys and enforces token quotas.
Do not put your Tavily API key directly into Cherry Studio. Always route traffic through Hikari by using its access token.
For the full HTTP proxy design and acceptance criteria, see docs/tavily-http-api-proxy.md.
exhaustedstatus is triggered automatically when upstream returns 432; scheduler skips those keys until UTC month rollover or manual recovery.- Each access token maintains a soft affinity to a single API key for a short time window. Within that window, the proxy prefers the same key when it remains active; when affinity expires or the key becomes exhausted/disabled, the next key is chosen by a global least‑recently‑used scheduler to keep load balanced across healthy keys. If all are disabled, the proxy falls back to the oldest disabled entries.
request_logscaptures request metadata, upstream payloads, and dropped/forwarded header sets for postmortem analysis.- High-anonymity behavior (header allowlist, origin rewrite, etc.) is detailed in
docs/high-anonymity-proxy.md.
Tavily Hikari relies on a zero-trust/ForwardAuth proxy to decide who can operate admin APIs. Configure the following environment variables (or CLI flags) to match your identity provider:
export ADMIN_AUTH_FORWARD_ENABLED=true
export FORWARD_AUTH_HEADER=Remote-Email
export FORWARD_AUTH_ADMIN_VALUE=admin@example.com
export FORWARD_AUTH_NICKNAME_HEADER=Remote-Name- Requests must include the header defined by
FORWARD_AUTH_HEADER. If its value equalsFORWARD_AUTH_ADMIN_VALUE, the caller is treated as an admin and can hit/api/keys/*privileged endpoints. FORWARD_AUTH_NICKNAME_HEADER(optional) is surfaced in the UI to show who is operating the console. When absent, the backend falls back toADMIN_MODE_NAME(if provided) or hides the nickname.- For purely local experiments you can set
DEV_OPEN_ADMIN=true, but never enable it in production.
If you cannot (or do not want to) run a ForwardAuth gateway, Tavily Hikari can expose a built-in admin login page backed by an HttpOnly cookie session.
export ADMIN_AUTH_BUILTIN_ENABLED=true
echo -n 'change-me' | cargo run --quiet --bin admin_password_hash
export ADMIN_AUTH_BUILTIN_PASSWORD_HASH='<phc-string>'
# Optional: disable ForwardAuth entirely if you are not using it.
export ADMIN_AUTH_FORWARD_ENABLED=false- When built-in login is enabled and the browser is not signed in, the public homepage shows an Admin Login button.
- Successful login sets an HttpOnly cookie (
hikari_admin_session) and unlocks admin-only APIs +/admin. - For production, prefer ForwardAuth. Built-in login is intended for small/self-hosted deployments.
- Avoid storing plaintext passwords in env vars. Prefer
ADMIN_AUTH_BUILTIN_PASSWORD_HASH(PHC string) and use a strong password. - Sessions are stored in-memory and expire server-side (aligned with cookie
Max-Age, default 14 days). Restarting the process logs users out. - The in-memory session store is bounded (evicts oldest sessions when the cap is exceeded) to avoid unbounded growth.
- If you terminate TLS at a reverse proxy, set
X-Forwarded-Proto: https(orForwarded: proto=https) so the backend can mark the session cookie asSecure.
- Avoid storing plaintext passwords in env vars. Prefer
Deployment example (Caddy as gateway): see examples/forwardauth-caddy/.
Tavily Hikari can expose Linux DO Connect OAuth2 login for regular users, independent from admin auth.
export LINUXDO_OAUTH_ENABLED=true
export LINUXDO_OAUTH_CLIENT_ID='<your-linuxdo-client-id>'
export LINUXDO_OAUTH_CLIENT_SECRET='<your-linuxdo-client-secret>'
export LINUXDO_OAUTH_REDIRECT_URL='https://tavily.ivanli.cc/auth/linuxdo/callback'- Homepage behavior:
- When not logged in, area ① shows a Sign in with Linux DO button.
- After login, area ① is hidden and area ② auto-fills the user's bound
th-...token.
- Token policy:
- First Linux DO login automatically creates and binds one Hikari access token.
- Later logins reuse the same binding; no extra token is created.
- If the bound token is disabled/deleted,
/api/user/tokenreturns an error (404or409) and does not auto-regenerate.
- New endpoints:
GET /auth/linuxdoGET /auth/linuxdo/callbackGET /api/user/tokenPOST /api/user/logout
- Built with React 18, TanStack Router, shadcn/ui (Radix), Tailwind, Iconify.
- Displays live key table, request log stream, and admin-only actions (copy real key, restore, delete).
scripts/write-version.mjsstamps the build version into the UI during CI releases.bun run devproxies/api,/mcp, and/healthto the backend to avoid CORS hassle during development.
Operator and integration views of Tavily Hikari.
Tavily Hikari speaks standard MCP over HTTP and works with popular clients:
- Codex CLI
- Claude Code CLI
- VS Code — Use MCP servers
- GitHub Copilot — GitHub MCP Server
- Claude Desktop
- Cursor
- Windsurf
- Any MCP client supporting HTTP + Bearer token auth
Example (Codex CLI — ~/.codex/config.toml):
experimental_use_rmcp_client = true
[mcp_servers.tavily_hikari]
url = "https://<your-host>/mcp"
bearer_token_env_var = "TAVILY_HIKARI_TOKEN"
Then set the token and verify:
export TAVILY_HIKARI_TOKEN="<token>"
codex mcp list | grep tavily_hikari
- Rust toolchain pinned to 1.91.0 via
rust-toolchain.toml. - Common commands:
cargo fmt,cargo clippy -- -D warnings,cargo test --locked --all-features,cargo run -- --help. - Frontend (Bun, pinned via
.bun-version):bun install --frozen-lockfile,bun run dev,bun run build(runstsc -b+vite build). - Hooks: run
lefthook installto enable automaticcargo fmt,cargo clippy,bunx dprint fmt, andbunx commitlint --editon every commit. - CI:
.github/workflows/ci.ymlruns lint/tests/build. - Release:
.github/workflows/release.ymlruns after main CI succeeds and publishes tags, GitHub Releases, and GHCR images.
Releases are label-driven:
- Every PR must have exactly one intent label:
type:patch,type:minor,type:major,type:docs, ortype:skip. - Every PR must have exactly one channel label:
channel:stableorchannel:rc. - When a PR is merged into
mainand CI passes, the release workflow computes the next stable semver (X.Y.Z) and publishes:- Git tag + GitHub Release
- GHCR image tags:
- stable (
channel:stable):latest,vX.Y.Z - prerelease (
channel:rc):vX.Y.Z-rc.<sha7>(nolatest)
- stable (
- If a commit cannot be mapped to exactly one PR, release is skipped (conservative default).
- Only expose
/mcp,/api/*, and static assets; everything else returns 404. - Protect admin APIs/UI via ForwardAuth or another zero-trust proxy so regular users never see real keys.
- Follow the header sanitization guidance in
docs/high-anonymity-proxy.mdwhen operating in high-anonymity environments. - Persist
tavily_proxy.dbvia volumes or external storage and exportrequest_logsfor compliance if needed.
Distributed under the MIT License. Keep the license notice intact when copying or distributing the software.


