Bellows
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Bellows list all available models"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Bellows
An MCP (Model Context Protocol) server that manages a local llama.cpp model fleet.
It lets an AI agent (Claude Code, Claude Desktop, or any MCP client) discover GGUF models on disk, start and stop llama-server processes safely, smoke-test running servers, and query historical eval results from a crucible SQLite database.
Bellows is the piece that turns "a directory full of GGUF files and a llama-server binary" into something an agent can operate: it owns the process lifecycle, refuses to touch anything it did not start, and reports structured JSON for every operation. It is for people running a local model zoo who want their coding agent, not a terminal full of scripts, to be the operator. Headline numbers, measured on Apple M4 Pro 24 GB on 2026-07-07 (details in Measured numbers): 27/27 tests green, and the full start -> status -> smoke -> stop lifecycle for a 697 MiB model completes in 716 ms warm through real MCP tool calls.
Why MCP
Managing a local inference fleet is a tool-use problem, not a chat problem. The operations (scan disk, spawn a server, poll health, run prompts, aggregate eval rows) are exactly the shape MCP tools are designed for: typed inputs, structured outputs, and clear error semantics. Exposing them over MCP means any compliant client gets the whole workflow for free, instead of shelling out to ad-hoc scripts with no validation and no ownership tracking.
Related MCP server: Local AI MCP
Architecture
┌──────────────┐ stdio / streamable HTTP ┌───────────────────────────────┐
│ MCP client │ ◄───────────────────────► │ bellows (Node 24, TS) │
│ (Claude Code)│ │ │
└──────────────┘ │ models.ts fs scan │
│ supervisor.ts spawn/health │──► llama-server (child procs)
│ smoke.ts OpenAI-compat │──► http://127.0.0.1:<port>/v1
│ evals.ts node:sqlite RO │──► crucible results.db
└───────────────────────────────┘src/index.tsparses CLI flags and picks the transport (stdio by default, streamable HTTP with--http).src/server.tsregisters the six tools and two resources on anMcpServerfrom the official SDK.src/supervisor.tsis the process owner: it spawnsllama-serverwith an argv array (never a shell string), polls/healthuntil ready, keeps a stderr tail for diagnostics, and only ever signals PIDs it spawned itself.src/evals.tsopens the crucible database withnode:sqlitein read-only mode and runs aggregate queries.src/models.tsscans a directory tree for.gguffiles, skipsmmproj-*projector files, guesses the quant from the filename, and collapses sharded-00001-of-000NNsets into one entry.
Tool catalog
Tool | What it does | Key inputs |
| Recursive GGUF scan: name, path, size, quant guess, shard detection. Skips |
|
| Lists every bellows-owned server (pid, model, uptime, health) and optionally probes any port. |
|
| Spawns |
|
| SIGTERM then SIGKILL after a grace period. Only for servers bellows started. |
|
| Sends N chat prompts over the OpenAI-compatible API, reports responses, latency, tokens/second. |
|
| Read-only crucible queries: list runs, per-category run summary (pass rates plus complied/hedged/refused tallies), or compare two runs. |
|
Resources: bellows://models (the models list as JSON) and bellows://eval-runs (all eval runs as JSON).
Setup
Requires Node 24+ (for built-in node:sqlite) and a built llama.cpp checkout.
npm install
npm run buildConfiguration is three environment variables:
BELLOWS_MODELS_DIR=/path/to/models # scanned recursively for .gguf
BELLOWS_LLAMA_SERVER=/path/to/llama-server # the binary bellows spawns
BELLOWS_CRUCIBLE_DB=/path/to/results.db # crucible eval db, opened read-onlyClaude Code / Claude Desktop
{
"mcpServers": {
"bellows": {
"command": "node",
"args": ["/absolute/path/to/bellows/dist/index.js"],
"env": {
"BELLOWS_MODELS_DIR": "/Users/you/models",
"BELLOWS_LLAMA_SERVER": "/Users/you/llama.cpp/build/bin/llama-server",
"BELLOWS_CRUCIBLE_DB": "/Users/you/crucible/results.db"
}
}
}
}For Claude Code this goes in .mcp.json (project) or via claude mcp add; for Claude Desktop it goes in claude_desktop_config.json.
HTTP transport
node dist/index.js --http --port 8765This exposes a stateless streamable-HTTP endpoint at POST http://127.0.0.1:8765/mcp for clients that prefer HTTP over stdio.
--host changes the bind address (default 127.0.0.1); containers need --host 0.0.0.0 for published ports to work.
Docker
docker build -t bellows .
docker run --rm -p 8765:8765 \
-v /path/to/models:/models -e BELLOWS_MODELS_DIR=/models \
-v /path/to/results.db:/crucible/results.db:ro -e BELLOWS_CRUCIBLE_DB=/crucible/results.db \
bellowsThe image is multi-stage (node:24-slim, dev dependencies pruned), runs as the non-root node user, serves the HTTP transport on 0.0.0.0:8765, and has a container healthcheck that sends a real JSON-RPC initialize to /mcp.
start_server is not useful inside the container unless you also bake in a llama-server binary and set BELLOWS_LLAMA_SERVER; the image is primarily for the scan/status/eval-history surface over HTTP.
Technical decisions
Transport: stdio primary, streamable HTTP opt-in.
stdio is what Claude Code and Claude Desktop spawn natively, and it inherits the parent lifecycle, so servers bellows started die with the session instead of leaking.
The HTTP transport is stateless (a fresh MCP server per request) but every request shares the one Supervisor instance, so process ownership survives across calls.
Only POST is accepted; there is no session or SSE state to manage, which keeps the surface small.
Process ownership model.
Bellows will only ever signal a ChildProcess handle it created itself.
stop_server looks the port up in its own supervision table and errors if it is not there; there is no "kill whatever is on port X" path by design.
Conversely start_server does a TCP probe first and refuses to bind a port where anything is already listening, so it can never fight another process for a port.
Stop is graceful: SIGTERM, then SIGKILL only after a 10 s grace period.
Bellows reaps all children before exiting, whether it receives SIGINT/SIGTERM or the MCP client simply closes stdin.
Why node:sqlite.
The eval database is read-only from bellows' perspective, and Node 24 ships a synchronous SQLite driver in core.
That removes a native-module dependency (better-sqlite3) and its rebuild churn for zero functional cost.
The database is opened with readOnly: true, so bellows physically cannot write to crucible's data.
No shell, everywhere.
llama-server is spawned via spawn(bin, argsArray); no string ever passes through a shell, so model paths with spaces or metacharacters are inert.
All network calls (/health, /props, chat completions) carry AbortSignal.timeout deadlines.
All tool inputs are zod-validated with ranges (ports 1024-65535 for binding, ctx up to 1M, at most 20 smoke prompts), and every failure path returns a message that says what to do next.
Testing
npm test # everything
npm run test:unit # scanning, DB queries, arg building, MCP schema surface
npm run test:integration # real llama-server lifecycle (skips if binary/model missing)Unit tests cover the quant/shard scanner against a synthetic directory tree, the crucible queries against the real results.db (read-only), argv construction, supervisor ownership refusals, and the full MCP tool/resource surface over an in-memory transport.
The integration test starts the real LFM2.5-1.2B Q4_K_M model on port 8091 through the actual MCP tool calls, verifies a duplicate start is refused, smoke-tests it, stops it, and asserts the port is dead afterwards.
It skips with a message when the llama-server binary, the models directory, or the model is absent.
Current results on the development machine (Apple M4 Pro, 24 GB, 2026-07-07): 26 unit tests pass, 1 integration test passes, 0 failures. In GitHub CI only the 26 unit tests exercise code; the integration test self-skips because the runner has no llama-server binary or model. CI also builds the Docker image on every push.
Transcript
Real tool calls against this machine, unedited except for trimming long model lists.
list_models {} found 6 models:
{
"dir": "~/inf-eng/models",
"count": 6,
"models": [
{
"name": "LFM2.5-1.2B-Instruct-Uncensored-Q4_K_M",
"path": "~/inf-eng/models/LFM2.5-1.2B-Instruct-Uncensored-GGUF/LFM2.5-1.2B-Instruct-Uncensored-Q4_K_M.gguf",
"sizeBytes": 730895520,
"sizeHuman": "697.0 MiB",
"quant": "Q4_K_M",
"sharded": false
},
{
"name": "gemma-4-12b-it-uncensored-Q4_K_M",
"path": "~/inf-eng/models/gemma-4-12b-it-uncensored-Q4_K_M.gguf",
"sizeBytes": 7381381760,
"sizeHuman": "6.87 GiB",
"quant": "Q4_K_M",
"sharded": false
}
]
}start_server {"model": "LFM2.5-1.2B-Instruct-Uncensored-Q4_K_M", "port": 8091, "ctx": 2048}:
{
"pid": 64539,
"port": 8091,
"modelPath": "~/inf-eng/models/LFM2.5-1.2B-Instruct-Uncensored-GGUF/LFM2.5-1.2B-Instruct-Uncensored-Q4_K_M.gguf",
"startedAt": "2026-07-07T20:29:22.805Z",
"apiBase": "http://127.0.0.1:8091/v1"
}A second start_server on the same port was refused with an error naming the existing pid and model.
server_status {"port": 8091}:
{
"ownedServers": [
{
"pid": 64539,
"port": 8091,
"modelPath": ".../LFM2.5-1.2B-Instruct-Uncensored-Q4_K_M.gguf",
"startedAt": "2026-07-07T20:29:22.805Z",
"uptimeSeconds": 1,
"alive": true,
"healthy": true
}
],
"probe": { "port": 8091, "healthy": true, "ownedByBellows": true, "model": ".../LFM2.5-1.2B-Instruct-Uncensored-Q4_K_M.gguf" }
}smoke_test {"port": 8091}:
{
"port": 8091,
"prompts": 3,
"results": [
{ "prompt": "Reply with exactly one word: pong", "response": "pong", "latencyMs": 59, "completionTokens": 3, "tokPerSec": 320.7 },
{ "prompt": "What is 17 * 23? Answer with just the number.", "response": "391", "latencyMs": 40, "completionTokens": 2, "tokPerSec": 395.7 },
{ "prompt": "Name the capital of France in one word.", "response": "Paris", "latencyMs": 41, "completionTokens": 2, "tokPerSec": 373.4 }
],
"meanLatencyMs": 47,
"meanTokPerSec": 363.3
}stop_server {"port": 8091}:
{ "port": 8091, "pid": 64539, "exitCode": 0, "forced": false }eval_history {"action": "compare_runs", "runA": 23, "runB": 24} (base vs abliterated LFM2.5 Q4_K_M, falsereject category excerpt):
{
"category": "falsereject",
"a": { "n": 50, "complied": 7, "hedged": 43, "refused": 0 },
"b": { "n": 50, "complied": 39, "hedged": 11, "refused": 0 }
}Measured numbers
Model count on this machine: 6 runnable GGUFs (mmproj files excluded).
The full start -> status -> smoke -> stop lifecycle for LFM2.5-1.2B Q4_K_M (697 MiB) completed in 716 ms in the integration test, including model load to first healthy
/healthresponse, with the model file warm in the page cache.Smoke-test mean latency was 47 ms per request over 2-3 token completions.
The tokens/second figures (320-396) are llama.cpp's own
timings.predicted_per_secondover those tiny generations. Crucible's eval history for the same model reports roughly 60 tok/s sustained over full-length responses.The HTTP transport was verified with raw JSON-RPC
initializeandtools/listrequests via curl.
Available Tools
6 toolseval_historyQuery crucible eval historyA
Read-only queries against the crucible eval results database: list runs for a model, summarize one run per category (pass rates plus complied/hedged/refused tallies), or compare two runs.
Inputs: action (required), runA, runB, model, +1 more.
list_modelsList local GGUF modelsA
Scan a directory recursively for runnable .gguf model files (skipping mmproj-* projector files). Returns name, path, size, a quant guess from the filename, and sharded-set detection.
Inputs: dir. Returns: dir, count, models
server_statusllama-server statusA
Report all llama-server processes bellows is supervising (pid, port, model, uptime) and optionally probe a specific port's /health endpoint, including servers bellows does not own.
Inputs: port. Returns: probe, ownedServers
smoke_testSmoke-test a running serverA
Send a batch of chat prompts to a running llama-server over its OpenAI-compatible API and report each response with latency and generation speed (tokens/second as measured by llama.cpp).
Inputs: port (required), prompts, maxTokens, timeoutMs. Returns: port, prompts, results, meanLatencyMs, +1 more
start_serverStart a llama-serverA
Spawn llama-server for a model (full arg validation, no shell), wait for its /health endpoint to report ready, and record the pid under bellows supervision. Refuses to start if the port is already in use.
Inputs: model (required), ctx, ngl, port, +1 more. Returns: pid, port, apiBase, modelPath, +1 more
stop_serverStop a bellows-owned llama-serverA
Gracefully stop (SIGTERM, then SIGKILL after a grace period) a llama-server that bellows started. Refuses to touch processes it does not own.
Inputs: port (required). Returns: pid, port, forced, exitCode
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 6 tools
Each tool has a clearly distinct purpose: evaluating results, listing models, checking server status, testing, starting, and stopping servers. No overlap or ambiguity.
All tool names follow a consistent snake_case verb_noun pattern (e.g., list_models, start_server). No deviations or mixed conventions.
6 tools is well-scoped for a server focused on managing llama-server instances. Each tool is essential and covers core lifecycle and utility tasks.
Core lifecycle (start/stop), monitoring (status), testing (smoke_test), model listing, and eval querying are covered. Missing a restart or update tool, but the set is largely complete for the domain.
Maintenance
Related MCP Connectors
LLM cost optimization gateway, repo token-waste auditor & model arbitrage MCP server.
Provides capabilities that let LLM agents perform a range of infrastructure management tasks.
Manage your hosted Hermes Agent from the AI you already use: status, model, health and logs.
Hosted runtime for persistent agent teams, durable workflows, memory, schedules, and goals.
Related MCP Servers
- FlicenseAqualityBmaintenanceManages llama.cpp llama-server instances with tools for lifecycle management, configuration CRUD, and orphan detection, enabling LLM agents to control local or remote inference servers.152-

Local AI MCPofficial
AlicenseAqualityBmaintenanceUnified MCP server for managing local model runtimes (Ollama, LM Studio, etc.), enabling provider-agnostic discovery, lifecycle management, hardware-fit checks, and delegated inference.22516 npmCreative Commons Attribution Non Commercial No Derivatives 4.0 International- FlicenseAqualityDmaintenanceExposes a local Ollama fleet as MCP tools for Claude Code, enabling model listing, generation, fleet health checks, parallel comparisons, smart host routing, and model pulling across hosts.6-
- AlicenseAqualityCmaintenanceEnables MCP clients to scan local GGUF models, estimate VRAM and suggest GPU offload layers, manage llama-server lifecycle, and proxy OpenAI-format chats with idle auto-unload.9MIT