
Stack Guardian
io.github.LuminariSoftwares由 GitHub 帳號 LuminariSoftwares 發布v0.4.0更新於 Oct 24, 2026
Health doctor for a home AI stack: Ollama nodes, GPUs and tailnet, with graded confidence.
概覽
唯讀 MCP 伺服器,用來診斷家用 AI 堆疊(Ollama 或 OpenAI 相容節點、NVIDIA GPU、tailnet 節點),提供分級且附證據的檢查結果。
- 可以做什麼
- 立即評估整個堆疊的健康狀態:節點、模型、GPU 與 tailnet,並列出發現項目與信心等級
- 取得未分級的原始快照:端點、模型 ID、VRAM 數據與 tailnet 狀態
- 解釋某個發現代碼:代表什麼、可以嘗試什麼,以及需要哪些證據才能確認
- 上述能力都可限定在單一指定節點
- 適用情境
- 適合在助理需要回答本機多機模型堆疊為何變慢或連不上,並希望得到分級、有證據的診斷而非猜測時使用。對象是在家中執行 Ollama 或 OpenAI 相容服務(LM Studio、llama.cpp、vLLM、LiteLLM)的使用者,也可跨 tailnet 使用。
- 執行需求
- 以本機 stdio 程序執行(PyPI 套件 stack-guardian,可用 uvx 啟動),需要 Python 3.11+,沒有執行期相依套件。需要一份 nodes JSON 檔,透過 STACK_GUARDIAN_NODES 環境變數或命令列參數指定,描述要探測的端點。節點可選的 api_key_env 指定存放 bearer 權杖的環境變數名稱。GPU 檢查需要本機有 nvidia-smi,tailnet 檢查需要 PATH 中有 tailscale。僅支援桌面環境。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 Stack Guardian,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
stack-guardian
[ci] [privacy] [XERJ Corpus Hub contributor] [stack-guardian MCP server – quality and maintenance score on Glama] [PyPI]
Status: active development -- changelog
mcp-name: io.github.LuminariSoftwares/stack-guardian
14 health checks, one command: the example stack exits 2 with 4 FAIL findings, on purpose.
A health doctor for a home AI stack -- Ollama or OpenAI-compatible nodes, NVIDIA GPUs, tailnet peers -- with graded, evidence-backed findings.
Quick start (60 seconds)
macOS / Linux (offline example run, Python 3.11+):
Windows (PowerShell), or anywhere you would rather not touch the system Python
pipx puts it in its own virtualenv and on your PATH (pip install pipx if you do not have it). uvx stack-guardian doctor --snapshot snapshot.json runs it without installing anything at all.
The example snapshot makes every shipped rule fire once, so you see each finding type without touching a real stack.
The command exits 2 because the example contains FAIL findings; that is the expected result. Each line ends with
the confidence grade - one run is at most (LIKELY), the same finding on a second run is (VERIFIED).
Point it at your own machines next:
init probes http://127.0.0.1:11434 to see whether Ollama is answering, and lists your tailnet peers (if
tailscale is on PATH) as commented-out candidates you can move up into the file. Edit it by hand from there; the
shape is in examples/nodes.json and under What it does.
From a clone instead (for writing rules or running the tests):
Proof
Run 2026-11-05 on stack-guardian 0.4.0 (): the command exits 2, prints worst: FAIL, and ends with:
14 findings on the example snapshot: 4 FAIL, 4 WARN, 6 INFO. The 14 checks that ship are counted from the installed package: stack-guardian 0.4.0 checks loaded from installed package: 14
Tests, 2026-11-05 (): 171 passed in 61.00s (0:01:00)
Standard library only, Python 3.11+, no runtime dependencies. GPUs: NVIDIA only, and only on the machine running stack-guardian.
A health doctor for a home AI stack. It collects a snapshot of your nodes (Ollama or OpenAI-compatible endpoints,
loaded models, NVIDIA GPUs, tailnet peers), runs small Python rules against it, remembers the last runs, and reports
findings with a confidence grade: one sample is at most LIKELY, the same finding twice in a row is VERIFIED. It
exists because "the model is slow" on a multi-machine local stack has a dozen possible causes, and an agent or a person
should get a graded, evidence-backed answer instead of a guess. A finding can carry a typed fix that you run on
purpose, never automatically. It ships as a CLI, an MCP server for coding agents, a tiny HTTP endpoint with Prometheus
metrics, and a DeepSeek Harness (DSH) plugin.
Two runs against examples/nodes.json with one state file:
That is the real output of python examples/demo_report.py: the shipped collector and rules, with a stub Ollama on
127.0.0.1 answering for gpu-box, the two placeholder tailnet addresses never contacted, and sample nvidia-smi and
tailscale output so it prints the same on any machine. The shipped rules only report problems; the OK line comes
from a small rule inside the script. A test fails if this block and the script disagree.
Why the grade. One run can make a finding at most LIKELY; the same finding on two runs in a row makes it
VERIFIED; passing checks are not graded. On a home network a sleeping laptop, a Wi-Fi drop or a model being
reloaded looks broken for a few seconds, so one bad sample is reported as a likely problem and only what persists is
reported as fact.
Standard library only, Python 3.11+, no runtime dependencies.
One of four small, independent tools for people running local models: see The guardian family.
Not affiliated with StackGuardian, the infrastructure-automation company. Same word, unrelated projects.
Architecture
Design decisions
Each is written up with its alternatives in docs/adr/.
- Standard library only (ADR 0001).
pip installpulls nothing else, so it installs on any machine with Python 3.11. Trade-off: the HTTP client, JSON-RPC loop and Prometheus renderer are hand-written and have to be tested here. - Rules are plain Python files in a folder (ADR 0002). A rule is a
function
(snapshot, ctx) -> finding | list | None; a rule that raises becomes acheck.crashedWARN, so a buggy rule never reports a broken stack. Trade-off: rules run in-process with your privileges; only load rule folders you trust. - Confidence from history, not from one sample (ADR 0003). One run
is at most
LIKELY; two bad runs in a row areVERIFIED. Trade-off: a real outage needs a second run before it isVERIFIED, and the history is a local file. - Typed fixes, dry run by default (ADR 0004). Exactly three actions, never
a shell string from a rule; nothing changes without
--apply, and every applied fix leaves a snapshot and an audit line. Trade-off: anything outside the three actions has to be done by hand. - Agent-facing surfaces are read-only (ADR 0005). The MCP server and
the HTTP endpoint never fix, never write the run history, and
servebinds to loopback unless told otherwise. Trade-off: an agent can diagnose but must hand the fix back to a person.
For coding agents
- MCP server (any MCP client):
stack-guardian-mcpspeaks MCP over stdio with three read-only tools,stack_health,stack_snapshotandstack_explain. See MCP server for client configs and the tool reference. - DSH plugin (
npm install dsh-stack-guardian, source indsh/, Node 20+): adds one line at the start of a DeepSeek Harness session when the doctor reports WARN or FAIL, and nothing otherwise. It runsstack-guardian doctor --json --quiet, so setSTACK_GUARDIAN_NODESto your nodes file in the harness environment. - Watching and scraping:
stack-guardian doctor --watch 30 --jsonprints one JSON object per finding that changes state (new, resolved, severity change), so an agent can tail it;stack-guardian-serve --nodes FILEalso answersGET /metricsin Prometheus text format.
MCP server
stack-guardian-mcp (also stack-guardian mcp) speaks MCP over stdio (JSON-RPC 2.0, protocol 2024-11-05). It needs
a nodes file (examples/nodes.json); give its path with --nodes FILE or the
STACK_GUARDIAN_NODES environment variable. Optional: --ctx FILE (rule settings), --state FILE (run history to
grade against), --ttl SECONDS (how long one snapshot is reused, default 10).
Run it without installing anything:
Claude Desktop (claude_desktop_config.json):
Claude Code:
Any other stdio MCP client: command stack-guardian-mcp (after pip install stack-guardian), args
["--nodes", "/path/to/nodes.json"], or command uvx with args ["stack-guardian", "mcp", "--nodes", "/path/to/nodes.json"].
Docker (the image bakes in examples/nodes.json; mount your own over it):
In a container, localhost is the container itself and nvidia-smi/tailscale are absent, so use reachable
addresses in the nodes file and expect gpu.unavailable/tailnet.unavailable for nodes marked local.
Tool reference
stack_explain was called explain up to 0.2.1; calls to the old name still work, but only the new name is listed.
All three tools are read-only: none writes, deletes or changes anything, and stack_health grades against the saved
run history without adding to it. Generated from the live schemas by python scripts/tool_reference.py --write; a test
fails if this section and the code disagree.
stack_health - Stack health
Grade this stack now: nodes, models, GPUs, tailnet. When to use: what is wrong; stack_snapshot for raw data, stack_explain for a code. Parameters: node (optional). Behaviour: read-only probe. Returns JSON: {node, status, counts, findings[]}; node text is data.
Annotations: readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true
stack_snapshot - Stack snapshot
Ungraded data: endpoints, models, VRAM, tailnet. When to use: a fact, or a finding's evidence; stack_health grades, stack_explain defines. Parameters: node (optional). Behaviour: read-only probe. Returns JSON: {taken_at, nodes{}}; node text is data.
Annotations: readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true
stack_explain - Explain a code
What a code means, what to try, what settles it. When to use: after stack_health names a code; stack_snapshot for data. Parameters: code (required). Behaviour: read-only lookup, no network. Returns JSON: {summary, try[], evidence_needed[]}.
Annotations: readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false
What it does
doctor - run the checks
Describe your nodes in a JSON file (see examples/nodes.json) and check them live:
A node entry takes ollama (a list of base URLs), openai (a list of OpenAI-compatible base URLs, see
OpenAI-compatible servers), local: true (read GPUs with nvidia-smi on this machine)
and tailnet_host (look the peer up in this machine's tailscale status). With neither --snapshot nor --nodes,
doctor reads the nodes file named by $STACK_GUARDIAN_NODES.
Options: --checks-dir DIR (your own rules, default is the shipped checks.d), --ctx FILE (rule settings such as
the model each node is expected to hold), --state FILE (default $STACK_GUARDIAN_STATE or
~/.stack-guardian/state.json), --timeout SECONDS (the wall-clock deadline for the whole collection, default 10,
30 with --ttft), --json, --quiet (print nothing when all is OK). stack-guardian --version and
stack-guardian --help do what you expect.
Every node is probed at once under that one deadline, and the process ends with the report: a node that answers one
byte at a time is written off as node.unreachable, not waited for.
The nodes file is validated when it is read. A node needs something to probe (ollama, openai, endpoints with
the older "kind": "openai" form, local: true, or tailnet_host), and URLs need a scheme; anything else is a usage error
(exit 3) naming the node, rather than a confident wrong answer. A single URL string where a list belongs is
accepted, with one note on stderr. Keys starting with _ are ignored, so a file can carry comments and candidate
nodes; "_options": {"timeout_s": 20} sets the deadline for that file.
Human output ends each line with the finding's confidence - (LIKELY) on one run, (VERIFIED) once the same
finding has been seen twice - and worst: X stays the last line. --json prints
{"schema": 1, "findings": [...], "worst": "..."}.
Exit codes: 0 all OK/INFO, 1 a WARN, 2 a FAIL, 3 usage, 4 the doctor itself crashed.
Shipped rules: node.unreachable, ollama.unreachable, openai.unreachable, openai.no_models, openai.slow,
model.not_loaded, model.expiring, model.slow_ttft, vram.unattributed, gpu.unavailable, gpu.parse_degraded, tailnet.unavailable, tailnet.peer_offline,
tailnet.relayed. Each raises
one finding per affected node. A rule that raises becomes a check.crashed WARN - a buggy rule never reports a broken
stack.
Servers that speak the OpenAI API (LM Studio, llama.cpp, vLLM, LiteLLM) are covered by OpenAI-compatible servers below.
model.expiring (WARN): a loaded Ollama model whose /api/ps expires_at is within 60 s of the snapshot time;
INFO when /api/ps is unavailable. Change the window with "model_expiring_s": 120 in --ctx.
--watch N re-runs the doctor every N seconds and prints only the findings that changed state, each with a
timestamp. Ctrl+C exits 0 with a one-line summary. With --json, each change is one JSON object on stdout.
--ttft sends a 1-token generation to every loaded model on each Ollama endpoint and records ttft_ms on the
model in the snapshot. It costs a generation, so it never runs unless you pass it, and it needs --nodes. The probe
keeps each model's unload time as it was. model.slow_ttft (INFO) fires above 2000 ms; change it with
"ttft_slow_ms": 1500 in --ctx.
OpenAI-compatible servers
LM Studio, llama.cpp's llama-server, vLLM and LiteLLM serve models over the OpenAI API. Give a node an openai
list of base URLs - the part before /models, usually ending in /v1 - beside or instead of ollama:
Each endpoint is probed with GET <base>/models, under the same single deadline as everything else. The snapshot
records, per endpoint, whether it answered, how long that took and the model ids it lists (node.openai[]; at most
200 ids, with models_total when there were more). api_key_env names an environment variable that holds the
bearer token for the node's openai endpoints; the token itself is not in the nodes file, the snapshot, the
state folder or any output, and is not forwarded if the server redirects to another host. If the variable is not
set, the endpoint is reported as failing with the variable's name and nothing is sent.
Confidence grading is the same as for Ollama. model.not_loaded counts a model an endpoint lists as present, because
the OpenAI API cannot say what is resident. Loaded-model state is Ollama-only: model.expiring and
model.slow_ttft (and --ttft) read only ollama endpoints, since /v1/models carries no unload time and a
time-to-first-token probe would cost a generation on a server this tool did not start. stack_health and
stack_snapshot over MCP include these nodes with no change to the tools.
Tested against: a stdlib stub server on 127.0.0.1 that answers GET /v1/models in the shape those servers
document (tests/test_openai_nodes.py). Also run once by hand, not in CI: a real
LiteLLM proxy 1.106.2 on 127.0.0.1 (no master key; it listed its two configured models, and answers on /v1/models
and /models alike). LM Studio, llama.cpp llama-server and vLLM: expected to work, not yet tested. The bearer token
was exercised against the stub only; a LiteLLM proxy with a master key needs its database, which was not set up.
The older form {"kind": "openai", "endpoints": ["http://127.0.0.1:1234"]} (the base URL without /v1) still works
exactly as before, including its ollama.unreachable finding; use openai for new nodes.
doctor --fix ID - run a finding's typed fix
A rule may attach fix={"action": ..., "args": {...}} to a finding. There are exactly three actions and never a raw
shell string:
That is a dry run: it prints the plan and changes nothing. Add --apply to perform it. An applied fix first saves
the snapshot under <state folder>/snapshots/, then runs, then appends one JSON line to <state folder>/fixes.jsonl
(--audit FILE to move it). A set_env value is never written to the audit, only its length. ID is the finding
code, or code@node when several nodes raise the same code. With --fix, the exit code is the fix's: 0 plan or
success, 1 refused or failed, 3 usage.
Other commands
-
stack-guardian init [--out nodes.json] [--ollama URL]writes a starter nodes file for this machine: it probes the local Ollama, lists tailnet peers as commented-out candidates, and refuses (exit3) rather than overwrite a file that is already there. -
stack-guardian snapshot --nodes FILE [--ttft] [--timeout SECONDS]prints the raw snapshot the rules read. -
stack-guardian-serve --nodes FILE [--ctx FILE] [--port 8788] [--cache SECONDS]serves a fresh snapshot atGET /snapshotand Prometheus metrics atGET /metrics(read-only, loopback unless you pass--host H --allow-remote). It is a scrape target, not a cluster protocol: no other part of stack-guardian reads it. Every request collects a new snapshot;--cache SECONDS(default0, off) reuses one for that long if a scrape interval is faster than your nodes answer. It has no auth: bind to localhost or a tailnet IP only. -
The same server answers
GET /metricsin Prometheus text format, running the shipped rules on a fresh snapshot:stack_guardian_finding{code,severity,node}(1 while raised, 0 once resolved),stack_guardian_worst_severity(0 OK, 1 INFO, 2 WARN, 3 FAIL) andstack_guardian_last_run_timestamp. -
stack-guardian-mcp --nodes FILE [--ctx FILE] [--state FILE] [--ttl SECONDS]: see MCP server.
Writing a rule
Drop the file in a folder and point --checks-dir at it. A full example with a fix is in
examples/checks.d/20_ollama_fix.py. A rule may also return a list of findings,
one per node.
Examples
Troubleshooting
By finding code - the same text stack_explain gives an agent:
And by symptom:
What leaves the machine, and what is written
- Out: HTTP(S) requests to exactly the base URLs in your nodes file (
/api/version,/api/tags,/api/ps, orGET <base>/modelsfor eachopenaiURL,/v1/modelsfor the olderkindform), and with--ttftone 1-token generation per loaded Ollama model. No credentials are sent unless a node setsapi_key_env, in which case that variable's value goes as a bearer token to that node'sopenaiURLs only (and is dropped if a redirect leaves the host); redirects are followed, and nothing is sent anywhere else: no telemetry, no update check, no analytics.nvidia-smiandtailscale status --jsonrun locally. - In: nothing is downloaded or installed at run time. The package has no runtime dependencies.
- Written: the state folder,
$STACK_GUARDIAN_STATEor~/.stack-guardian/, created0700. It holdsstate.json(the last 5 runs per node: codes and levels, no measurements) and, only after--fix ... --apply,fixes.jsonl(one audit line per applied fix) andsnapshots/(the snapshot taken just before it).set_envalso writesenv. Those files are created0600; aset_envvalue is never written to the audit, only its length.doctor --json,snapshotand the MCP server write nothing but their own output. - Shown: a snapshot contains your node names, model names, VRAM figures, GPU process names and tailnet state.
That is what
stack-guardian-serveserves and what an MCP client receives, so treat it as you wouldnvidia-smioutput.
Limits
What it does not do, so you are not surprised:
- GPUs: NVIDIA only, and only on the machine running stack-guardian. GPU data comes from
nvidia-smion a node marked"local": true. Remote nodes report endpoints, models and tailnet state, not VRAM. Nothing in the package reads another machine's/snapshot, so there is no way to pull a remote node's GPUs into this report: runstack-guardianon that machine too, or scrape itsstack-guardian-serve/metricswith Prometheus. No AMD, Intel or Apple GPU support. - Tailnet state is this machine's view. Peers are read from the local
tailscale status --json; a peer can be offline to you and online to another node. VERIFIEDneeds two runs. The MCP server never records a run, so on its own it reports at mostLIKELYunless the CLI (or--watch) has built up history in the same state file.- No auth on
stack-guardian-serve. It is read-only and loopback by default; it is not meant for the open internet. - MCP over stdio only, protocol
2024-11-05: results are JSON text content, withoutoutputSchemaorstructuredContent. - Rules run in-process. A rule folder is code; only point
--checks-dirat rules you trust. --ttftcosts a real generation per loaded model, which is why it is opt-in.
How it's tested
- Every module carries a counted
--selftest(pure functions, injected HTTP and command runners, no network); the suite runs each one. - Integration tests drive the real CLI and the MCP server over stdio in a subprocess, including the tool list and its
annotations, and run the
examples/exactly as this README shows them. tests/test_tool_definitions.pyfails if any MCP tool or parameter loses its description, type, example, default or constraints, if a shipped finding code has nostack_explainentry, or if the tool reference above is stale.tests/test_tools_list_budget.pystarts the real MCP server and fails if the wholetools/listpayload grows past its token budget, because those definitions are sent on every request.- CI runs the suite on Ubuntu, Windows and macOS (Python 3.11) and on Ubuntu with 3.12 and 3.13, builds the wheel and runs it from a clean virtualenv (so a release without its rules fails), builds the Docker image and lists its tools, syntax-checks the DSH plugin, and runs a privacy scan of every tracked file.
Contributing and security
Pull requests are welcome; CONTRIBUTING.md has the setup, the house style and the three rules that matter (a test you watched fail first, no new runtime dependency, the tool-definition token budget).
Found something exploitable? Please report it privately through the repository's Security tab, not a public issue:
SECURITY.md explains how, and lists what this tool does by design (rules are code, node URLs are
fetched, serve has no auth).
Credits
No code is copied from other projects. The wire formats follow their public specs: the
Model Context Protocol (JSON-RPC over stdio, tool annotations) and the
Prometheus text exposition format. The data comes from
the documented interfaces of Ollama, nvidia-smi and
tailscale status --json.
The guardian family
Four small MIT-licensed tools for people running local models. Each does one job, they share no code, and each is useful on its own.
One of four free, MIT-licensed guardians for people running local models, each answering one question from the outside: tool-guardian (what do my tools cost the window?), context-guardian (will this session hit the limit?), stack-guardian (is my stack healthy?), code-guardian (is this code safe to run?).
License
MIT. See LICENSE.
來源:README.md,提交 99d2f91
工具
0版本歷史
3- v0.4.0最新Oct 24, 2026
- v0.3.0Oct 22, 2026
- v0.2.1Oct 15, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分