llm-localfirst

llm-localfirst

A local-first LLM routing MCP server that keeps sensitive data on your own models, with fail-closed privacy and manager-worker delegation, exposing route and complete tools to any MCP client.

Category
访问服务器

README

llm-localfirst

Local-first LLM routing — keep sensitive data and bulk text labor on your own models, and call the cloud only for the hard part.

PyPI CI Python License: MIT

<p align="center"> <img src="https://raw.githubusercontent.com/shaxzodbek-uzb/llm-localfirst/main/docs/demo.svg" alt="llm-localfirst CLI: bulk work falls back to cloud; a sensitive call fails closed and never reaches the cloud" width="820"> </p>

Most LLM routers optimize which cloud provider to call for cost or failover. llm-localfirst inverts the default: it runs on your own local model first (Ollama / vLLM / LM Studio) and reaches for the cloud only when the work genuinely needs it. It adds two things mainstream routers don't:

  1. 🔒 Privacy routing that fails closed. A call you flag sensitive=True is pinned to a local model and is never allowed to fall back to the cloud. If the local model is down, the call raises — it does not quietly ship your prompt to a third-party API.
  2. 🤝 Manager–worker delegation. Give a cloud "director" agent a drop-in tool that offloads token-heavy, low-risk text labor (summarize / draft / translate / reformat / extract / classify) to a fast local worker — cutting cloud spend and keeping bulk data on your hardware. (Extracted from a production Pydantic AI agent.)

Plus a model allowlist guard (arbitrary model strings are rejected — an SSRF/cost blast-radius control), cached reachability probing, an MCP server wrapper, and a CLI.


The privacy guarantee, in five lines

from llm_localfirst import Router, LocalUnavailable

router = Router.from_env()
try:
    out = router.complete("Redact all PII from this record.",
                          source=customer_record, sensitive=True)
except LocalUnavailable:
    # Local model is down. We did NOT send the record to the cloud. You decide.
    ...

sensitive=True means this data must not leave the box. The router would rather fail than leak. That asymmetry — sensitive calls fail closed, ordinary bulk calls fall back to cloud — is the product.


Install

pip install llm-localfirst              # the routing brain — zero provider SDKs
pip install "llm-localfirst[openai]"    # + talk to local Ollama/vLLM/LM Studio (and cloud OpenAI)
pip install "llm-localfirst[anthropic]" # + Claude (the default cloud fallback / reason model)
pip install "llm-localfirst[all]"       # everything (also: mcp, pydantic-ai)
Extra Adds Needed for
(none) pydantic-settings router.decide(...) — pure routing, no calls
openai openai running calls on a local OpenAI-compatible server (or cloud OpenAI)
anthropic anthropic the default cloud fallback / reason model (Claude)
mcp mcp llm-localfirst mcp (expose the router over MCP)
pydantic-ai pydantic-ai-slim the manager-worker attach_worker integration

The decision path (decide()) imports no provider SDK, so you can inspect routing — and run the whole test suite — with nothing but the core installed.


60-second quickstart (Ollama)

ollama pull qwen2.5:7b          # any OpenAI-compatible local server works
pip install "llm-localfirst[openai,anthropic]"
export ANTHROPIC_API_KEY=sk-ant-...   # only needed for the cloud fallback / reason path
from llm_localfirst import Router, Kind

router = Router.from_env()

# 1) Inspect routing WITHOUT spending a token.
print(router.decide(kind=Kind.BULK))     # -> local  (cheap + private)
print(router.decide(kind="reason"))      # -> cloud  (the hard part)
print(router.decide(sensitive=True))     # -> local  (pinned; never cloud)

# 2) Actually run it. Bulk work prefers local, and falls back to cloud only if local is down.
print(router.complete("Summarize this in one sentence.",
                      source=long_text, kind=Kind.BULK).text)

Or from the shell:

llm-localfirst doctor                      # show config, the allowlist, and local up/down
llm-localfirst route "summarize this" --kind bulk
llm-localfirst route "redact this" --sensitive    # exits non-zero if local is down (fail-closed)

How routing decides

decide() probes whether your local model is reachable (cached), then applies these rules in order:

Call Local up Local down
sensitive=True local raises LocalUnavailable (fail-closed)
explicit model="<cloud>" + sensitive=True raises PrivacyViolation
kind="reason" cloud cloud
kind="bulk" / "auto" (default) local cloud fallback (fell_back=True)
explicit model="<name>" that allowlisted model (cloud blocked only when sensitive)

Any explicit model must be a name on the allowlist; an arbitrary string (or a stray URL) raises ModelNotAllowed. That allowlist is the SSRF / cost guard — a caller can never point the router at a new endpoint or an expensive model it wasn't configured with.


Manager–worker delegation (Pydantic AI)

Let a cloud director keep the planning and tool-calls, and offload the grunt text work to a local worker:

from pydantic_ai import Agent
from llm_localfirst import Router
from llm_localfirst.integrations.pydantic_ai import attach_worker

router = Router.from_env()
director = Agent("anthropic:claude-haiku-4-5", system_prompt="...")

# Adds a `delegate_to_worker(task, source)` tool that routes to your LOCAL model.
# attach_worker REFUSES a non-local worker, so delegated source text can't leak.
attach_worker(director, router, worker_model="local",
              on_delegate=lambda task, result: ...)  # optional observability hook

The director calls delegate_to_worker for summaries, drafts, translations, reformatting, and extraction; those run on your GPU instead of burning cloud tokens. See examples/manager_worker.py.


MCP-native

Expose the router to any MCP client (Claude Desktop, IDEs, agents) as two tools — route (dry decision) and complete:

pip install "llm-localfirst[mcp]"
llm-localfirst mcp        # serves over stdio

How it compares

llm-localfirst is not a general multi-provider gateway, and it isn't trying to be. To be clear and fair: LiteLLM and Bifrost can already route to local models (Ollama, vLLM) — local capability is not the differentiator. The differentiators are the fail-closed privacy pin, the manager-worker delegation tool, and a local-first default posture.

Capability llm-localfirst LiteLLM OpenRouter llmrouter-lib
Route to local models (Ollama/vLLM)
Default posture is local-first ❌ (cloud proxy)
Sensitive calls fail closed — never fall back to cloud
Manager-worker delegation tool (cloud→local)
Allowlist guard (reject arbitrary model strings)
Many cloud providers / load-balancing / caching ➖ (by design)

If you want a broad cloud gateway with dozens of providers, use LiteLLM. If you want your private data to stay local by construction and your bulk work to run on your own hardware, that's this library.


What this is NOT

  • Not a multi-cloud gateway. It ships one local backend + Claude (+ optional OpenAI). Add more by registering them on the allowlist; it won't grow a hundred provider shims.
  • Not a content classifier. You tag a call sensitive=True (or pick a kind). It does not guess whether your text is private — it enforces what you declare.
  • Not load-balancing / semantic caching / cost analytics. Those are gateway features; this is a routing policy with a privacy guarantee.
  • Not a prompt firewall. It controls where a call runs, not what's in it.

Configuration

All settings are read from the environment (prefix LF_) or a .env file. See .env.example. Highlights:

Variable Default Meaning
LF_LOCAL_BASE_URL http://localhost:11434/v1 local OpenAI-compatible endpoint
LF_LOCAL_MODEL_ID qwen2.5:7b local model id
LF_FALLBACK_MODEL haiku cloud model for non-sensitive fallback
LF_REASON_MODEL haiku cloud model for kind="reason"
LF_SENSITIVE_FAIL_CLOSED true keep sensitive calls from ever leaking
LF_PROBE_TTL 30.0 seconds to cache the reachability probe

Development

uv venv && uv pip install -e '.[dev]'
ruff check . && pytest

The routing brain (policy, registry, router, reachability) is covered 100% offline — no network and no provider SDKs required. Contributions welcome; see CONTRIBUTING.md.

License

MIT © Shaxzodbek Qambaraliyev / Blaze. See LICENSE.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选