openlocal

openlocal

A zero-dependency MCP and agent tool server for small local models that provides a measured, deterministic tool-calling interface with file, web, shell, and calculation tools.

Category
Visit Server

README

<div align="center">

openlocal

Open tools. Local brains. Zero cloud.

A tool belt that small local models can actually use β€” and the receipts to prove it.

CI PyPI Python Dependencies License

</div>


pip install openlocal
openlocal quickstart

That's it. quickstart finds the model server you already run β€” ollama, LM Studio, llama.cpp, vLLM β€” writes your config, measures which tool-calling protocol your model can actually drive, and runs a real task so you see it work:

$ openlocal quickstart
openlocal quickstart - looking for a model server...
found ollama at http://127.0.0.1:11434/v1 with 6 model(s)
wrote ~/.openlocal/models.toml
default model: qwen3-4b  (served as qwen3:4b)
measuring which tool protocol this model can drive...
  -> native tool calling

demo: Read notes.md and tell me how many lines it has.
  tool     file_read
  final

  The file notes.md has 3 lines.

You are set up. Try:
  openlocal run "list the files here and summarise what this project is"
  openlocal eval          # score this model on 15 deterministic tool tasks
  openlocal mcp           # serve these tools to Claude Code over MCP

Why openlocal

πŸͺΆ Zero dependencies Pure standard library. Installs in seconds, runs on a Raspberry Pi, and can't be broken by somebody else's release.
πŸ”Œ Works with what you have Anything that speaks the OpenAI /v1 API. One URL, no adapters, no accounts.
πŸ“Š Measured, not claimed 10 models from 9 vendors, scored on 17 deterministic tasks. No LLM judge β€” every check is a file diff or a regex.
🧰 One registry, two surfaces The same 8 tools power an MCP server (Claude Code, any MCP client) and a local-model agent loop.
🩹 Tuned on real failures Every ergonomic rule here was paid for by a model breaking. The lessons table is the part you can't get anywhere else.
🧩 Add a vendor by editing TOML New model family? Edit families.toml, run openlocal probe, send a PR. No Python needed.

The leaderboard

Same tasks, same tools, one model at a time, two runs each. tools is the protocol each model was measured driving β€” not the one its docs claim.

model vendor tools passed pass rate bad json tok/s
qwen3.8-4b Alibaba native 17/17 100% 0 40.4
glm-4.7-flash z.ai native 17/17 100% 0 39.9
qwen3.8-27b Alibaba native 17/17 100% 0 16.6
nemotron-nano-4b NVIDIA native 16/17 94% 0 54.9
gemma-4-e4b Google native 16/17 94% 0 50.3
gpt-oss-20b OpenAI native 16/17 94% 2ΒΉ 45.3
granite-4.1-3b IBM native 15/17 88% 4 66.0
llama-3.1-8b Meta native 14/17 85% 2 33.4
mistral-small-3.2 Mistral native 14/17 82% 0 11.1
phi-4-mini Microsoft native 13/17 79% 0 61.5

<sub>ΒΉ gpt-oss's harmony parser rejects some of its own tool calls (llama.cpp answers 500). The client's retry ladder recovers every one β€” the retries column in RESULTS.md keeps that honest.</sub>

Reproduce it: openlocal leaderboard --repeat 2

Why your small model fails at tools

A 3B model doesn't fail because it's stupid. It fails because tool output is a prompt, and most tool output is written for humans. Everything below was measured β€” kept when the score went up, reverted when it didn't.

what broke the fix measured effect
"What is line 300?" β†’ model answered line 311 number every line: 300| row 300 fixed in one turn, across models
Model walked a 500-line file one line per turn footers state facts, never commands ("file continues" β‰  "call again with…") phi: 12 wasted steps β†’ 2
Prose instead of a tool call prose with no tool = the final answer (native only) phi: 51 malformed replies β†’ 5
…but "Sure, I'll use file_grep" is a plan intent detection: plans get nudged, answers get accepted runs stopped ending on intentions
file_read(notes.md) typed as text prose call-syntax is parsed and executed wasted turns eliminated
{"limit": None}, trailing junk, split objects JSON repair: brace scan, tail closing, Python literals, fragment merge phi bad_json 5 β†’ 0
Model wrote the file before reading the source stale-write detection at finish time llama: 13 β†’ 15 / 15
Model guessed "44" and repeated it when nudged one turn of tool_choice: "required" granite recovered the task
Read a 4-line CSV perfectly, said the sum was 40 a calc tool β€” and a guard so it can't be used before reading granite: +2 tasks
Wrote the output file without ever reading the source thin-air-write detection at finish time qwen-4b over MCP recovered
Copied the N| reference prefixes into edit targets file_edit strips them when the raw text does not match edit_code: 10/10 models pass
Same call, forever (A→B→A→B) repeat guard replays the cached result llama pagination loop gone
Server 500s on its own tool-call syntax retry ladder: as-asked β†’ warmer β†’ same turn without tools gpt-oss: 11/15 β†’ 14/15
Reverted: one more system-prompt rule β€” granite 12/15 β†’ 11/15. Fewer rules win.

MCP is a first-class citizen, not a wrapper

openlocal eval --via-mcp reruns the whole eval suite with the tools served over a real stdio MCP server β€” schemas crossing the wire, results as content blocks, a subprocess per task. The scores match the in-process numbers, which is the point: what Claude Code experiences is what was measured.

Use it from Claude Code (or any MCP client)

pip install "openlocal[mcp]"
claude mcp add openlocal -- openlocal mcp

The same 10 tools your local model uses, now in Claude Code. finish stays behind β€” it's loop control, not a capability.

Commands

openlocal quickstart              # find a server, configure, probe, demo
openlocal run "goal"              # agent loop over your tools
openlocal run "goal" --url http://host:1234/v1     # no config at all
openlocal chat "hello"            # one plain turn, no tools
openlocal tools                   # what the model can call
openlocal probe                   # measure native vs JSON tool calling
openlocal eval                    # 17 deterministic tasks, pass/fail
openlocal eval --via-mcp          # same tasks, tools served over a REAL MCP server
openlocal leaderboard --repeat 2  # every configured model, one table
openlocal mcp                     # stdio MCP server
openlocal models / serve / stop / status / pull    # local llama-server management

smol is a shorter alias for the same CLI.

Configuration

Your workspace is ~/.openlocal (or any directory containing models.toml, or $OPENLOCAL_HOME). It holds models.toml, state.json, logs, downloads and eval results β€” never the installed package.

[engine.ollama]
type = "external"
base_url = "http://127.0.0.1:11434/v1"

[[model]]
id = "qwen"
engine = "ollama"
family = "qwen"              # tells openlocal this vendor's tool-calling quirks
served_model = "qwen3:4b"    # the exact name the backend knows
default = true
env default meaning
OPENLOCAL_HOME ~/.openlocal workspace directory
OPENLOCAL_BASE_URL – point every command at one /v1 server
SMOL_ROOT cwd sandbox root for the file and shell tools
SMOL_MAX_CHARS 8000 hard cap on one tool result
SMOL_SHELL_ALLOW dev commands comma list, or *
SMOL_TOOL_TIMEOUT 120 seconds before an MCP tool call is abandoned

The tools

file_list(path=".")                       list files and folders
file_read(path, start_line=1, limit=200)  read a file, one page at a time
file_write(path, content)                 write a file
file_edit(path, find, replace)            replace text exactly, leave the rest alone
file_append(path, content)                add to the end without touching what exists
file_grep(pattern, path=".")              search files
web_search(query, limit=5)                search the web (DuckDuckGo, or Tavily via key)
web_read(url, offset=0, max_chars=4000)   read a page as text
shell_run(command, timeout=60)            run one allowlisted command
calc(expression)                          exact arithmetic - models cannot count
finish(answer)                            agent loop only: end the task

House rules, enforced by tests: flat arguments with defaults, an example call in every description, one-line result headers, hard-capped output with a truthful continuation hint, and errors that name the next move β€” no such file: x - call file_list(".") to see the files that exist.

Architecture

tools/          one registry  β†’  mcp_server.py   (Claude Code speaks MCP to it)
                              β†’  agent.py        (a local model calls the same tools)
_http.py        the entire network layer, on urllib (this is why deps = 0)
runner.py       external servers, or llama-server processes it starts and owns
protocol.py     native tool_calls β†’ loose JSON β†’ repair β†’ symptom-specific nudge
families.toml   per-vendor quirks as DATA - the file contributors edit
evals/          17 deterministic tasks + a leaderboard across models

Contributing

Adding a model family takes no Python: edit families.toml, add your model to models.toml, then

openlocal probe --model your-model
openlocal eval  --model your-model

and paste the resulting row into the PR. See CONTRIBUTING.md.

Running local GGUF models on AMD hardware (ROCmFP4 + MTP speculative decoding) is documented in docs/ENGINE.md β€” optional, and not needed for anything above.

License

MIT Β© DevXV3

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured