ZimaTools
by bobdivx
README.md
# ZimaTools
Native **ZimaOS** app: MCP server (HTTP), web dashboard, and **exclusive GPU lease** arbiter.
Popcorn has priority over Ollama and agents. Only one GPU lease at a time.
Repo: https://github.com/bobdivx/zimatools
Based on: [zimaos-cursor-mcp](https://github.com/bobdivx/zimaos-cursor-mcp) v0.0.2 (history preserved).
## Stack
- Monorepo **pnpm workspaces**
- `apps/mcp` — TypeScript, [mcp-framework](https://www.mcp-framework.com/) `0.2.x`, **HTTP Stream** (`/mcp`) + optional stdio
- REST bridge **Hono** (`/api/*`) for the UI
- `apps/web` — **Astro + Preact + Tailwind CSS + DaisyUI**
- Single public port via Docker Compose (web proxies MCP + API)
## Architecture
```
zimatools/
apps/mcp/ MCP server + REST + GPU arbiter
apps/web/ Astro/Preact/DaisyUI UI (+ reverse proxy in Docker)
docker/ Dockerfiles
docker-compose.yml
docker-compose.gpu.yml
```
| Service | Access | Role |
|---------|--------|------|
| Public entry | **8484** → `:8080` | UI + proxy (`/api`, `/health`, `/mcp`) |
| MCP (localhost) | `127.0.0.1:8765` inside the shared netns | HTTP stream |
| REST (localhost) | `127.0.0.1:8766` inside the shared netns | Hono API |
---
## Deploy on ZimaOS / CasaOS
Docker Hub images: `bobdivx/zimatools-mcp` + `bobdivx/zimatools-web`.
**One public port (`8484`).** `web` uses `network_mode: service:mcp` (sidecar): both share one network namespace, so the proxy reaches API/MCP on `127.0.0.1`. This avoids CasaOS DNS/`bridge` isolation that causes **502 Bad Gateway**.
Validated on ZimaOS / CasaOS with Docker Hub images `bobdivx/zimatools-mcp:latest` and `bobdivx/zimatools-web:latest`.
### Steps
1. Uninstall any previous ZimaTools app (free port `8484`).
2. CasaOS → **Apps** → install a custom app.
3. Prefer the **YAML** tab: paste the compose below **as-is**, then install.
4. Or use the **Form** tab and match this checklist:
| CasaOS field | `mcp` service | `web` service |
|--------------|---------------|---------------|
| Image | `bobdivx/zimatools-mcp:latest` | `bobdivx/zimatools-web:latest` |
| Network type | `bridge` (OK for mcp) | **`service:mcp`** (required) |
| Port mapping | **`8484` → `8080` TCP** | *(empty — no ports)* |
| Main service / Web URL | Main = `mcp`, URL port `8484` | — |
5. Verify:
```bash
curl -s http://127.0.0.1:8484/health
# {"ok":true,"service":"zimatools",...}
```
Open in a browser: `http://<nas>:8484/` (dashboard) and `http://<nas>:8484/mcp` (setup page with Cursor instructions).
### Compose YAML (copy/paste)
```yaml
services:
mcp:
image: bobdivx/zimatools-mcp:latest
restart: always
ports:
- "8484:8080"
environment:
MCP_TRANSPORT: http
MCP_HOST: "0.0.0.0"
MCP_PORT: "8765"
MCP_ENDPOINT: /mcp
MCP_CORS_ORIGIN: "*"
API_PORT: "8766"
API_CORS_ORIGIN: "*"
ZIMAOS_API_BASE: http://127.0.0.1
ZIMAOS_API_TOKEN: ""
ZIMAOS_SSH_PASSWORD: ""
volumes:
- /var/run/docker.sock:/var/run/docker.sock
web:
image: bobdivx/zimatools-web:latest
restart: always
network_mode: "service:mcp"
depends_on:
- mcp
environment:
HOST: "0.0.0.0"
PORT: "8080"
API_UPSTREAM: http://127.0.0.1:8766
MCP_UPSTREAM: http://127.0.0.1:8765
x-casaos:
hostname: ""
index: /
is_uncontrolled: false
main: mcp
port_map: "8484"
scheme: http
title:
custom: ZimaTools
```
### Endpoints after install
| Use | URL |
|-----|-----|
| Dashboard | `http://<nas>:8484` |
| MCP setup page (browser) | `http://<nas>:8484/mcp` — HTML help + copy/paste `mcp.json` |
| MCP endpoint (Cursor / agents) | `http://<nas>:8484/mcp` — same path, Streamable HTTP |
| REST health | `http://<nas>:8484/health` |
Browsers get the setup page on `GET /mcp` (`Accept: text/html`). MCP clients (POST / session / SSE) are proxied to the MCP server.
Optional env on `mcp`: set `ZIMAOS_API_TOKEN` (file tools) and `ZIMAOS_SSH_PASSWORD` (Docker tools).
### Port conflicts (“ports already in use”)
CasaOS often rewrites the YAML on save. Keep these rules:
| Rule | Why |
|------|-----|
| Publish `8484 → 8080` **only on `mcp`** | `web` shares mcp’s network; it must not declare ports |
| `web` network type must stay **`service:mcp`** | If set to `bridge`, you get **502** |
| Do not add a second `8484` on `web` | Duplicate publish → “ports already in use” |
| Do not add extra `networks:` blocks CasaOS invents | Keep the sidecar simple |
Free the port, then reinstall:
```bash
docker ps --format '{{.Names}}\t{{.Ports}}' | grep -E '8484|8765|8766'
docker rm -f $(docker ps -aq --filter name=zimatools) 2>/dev/null
# In CasaOS: fully uninstall the old ZimaTools app
```
If `8484` is still taken, change **only** `mcp.ports` and `port_map` (e.g. `18484:8080` and `port_map: "18484"`). MCP URL becomes `http://<nas>:18484/mcp`.
### 502 Bad Gateway
The web proxy cannot reach the API. Check the Form tab:
1. `web` → Network type = **`service:mcp`** (not `bridge`)
2. `web` → no port mappings
3. `mcp` → `8484 → 8080`
4. Upstreams on `web`: `http://127.0.0.1:8766` and `http://127.0.0.1:8765`
Then recreate / restart the app.
### Optional NVIDIA GPU
Requires `nvidia-container-toolkit`. Add under the `mcp` service:
```yaml
runtime: nvidia
environment:
NVIDIA_VISIBLE_DEVICES: all
NVIDIA_DRIVER_CAPABILITIES: utility,compute
```
---
## Local development
Requirements: Node.js 20+, pnpm 10 (`corepack enable`).
```bash
git clone https://github.com/bobdivx/zimatools.git
cd zimatools
cp .env.example .env
# fill ZIMAOS_API_BASE / ZIMAOS_API_TOKEN / SSH if needed
pnpm install
pnpm --filter @zimatools/mcp build
```
Two terminals:
```bash
pnpm dev:mcp # MCP HTTP :8765 + REST :8766
pnpm dev:web # UI :4321 (proxies /api, /health, /mcp)
```
Stdio (local Cursor only):
```bash
pnpm --filter @zimatools/mcp dev:stdio
```
Local Docker build (without Hub pull):
```bash
docker compose up -d --build
# GPU: docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d --build
```
---
## MCP endpoint (HTTP)
Transport: **Streamable HTTP** (`http-stream`), not stdio-only.
- Production URL for agents: `http://<nas>:8484/mcp`
- Browser setup UI (same path): open `http://<nas>:8484/mcp` in Chrome/Firefox
- Dev (direct MCP process): `http://localhost:8765/mcp`
- Methods: `POST` / `GET` / `DELETE` / `OPTIONS`
- CORS open by default (`MCP_CORS_ORIGIN=*`, `MCP_HOST=0.0.0.0`)
### Cursor / DevForge (`mcp.json`)
Add to `~/.cursor/mcp.json` (or your client MCP settings):
```json
{
"mcpServers": {
"zimatools": {
"url": "http://zimacube.local:8484/mcp"
}
}
}
```
Then reload MCP / restart Cursor. Tools `gpu.*` and ZimaOS file/Docker tools should appear.
### Local stdio (no NAS)
```json
{
"mcpServers": {
"zimatools": {
"command": "node",
"args": ["<path>/apps/mcp/dist/index.js", "--stdio"],
"env": {
"ZIMAOS_API_BASE": "http://zimacube.local",
"ZIMAOS_API_TOKEN": "..."
}
}
}
}
```
### REST (same GPU tools)
Via the public proxy: `http://<nas>:8484/...`
- `GET /health`
- `GET /api/gpu/status`
- `POST /api/gpu/acquire` `{ "client": "popcorn" }`
- `POST /api/gpu/release` `{ "client": "popcorn" }`
- `GET /api/gpu/queue`
- `POST /api/gpu/priority` `{ "client": "ollama", "priority": 50 }`
- `GET /api/apps` — inventaire conteneurs
- `GET /api/apps/watchdog` — config + état watchdog images
- `PATCH /api/apps/watchdog` `{ "enabled": true, "intervalMinutes": 60 }`
- `POST /api/apps/watchdog/run` — scan manuel (pull + recreate si digest change)
- `POST /api/apps/webhook` — déclenchement immédiat (header `Authorization: Bearer $IMAGE_WEBHOOK_SECRET` ou `X-Webhook-Secret`) ; body optionnel `{ "image": "repo:tag" }`
- `GET /api/docker/containers`
---
## MCP tools
### ZimaOS files (API)
`read_file_from_zimaos`, list / write / edit / search / mkdir / stats — see `apps/mcp/src/tools/`.
### Docker (SSH or docker.sock)
`list_docker_containers_zimaos`, start / stop / restart / logs / info.
### GPU (in-memory queue + nvidia-smi)
| Tool | Role |
|------|------|
| `gpu.status` | nvidia-smi + lease + queue. If `nvidia-smi` missing: **stub**. |
| `gpu.acquire` | Exclusive lease. Higher priority **preempts**. |
| `gpu.release` | Release and promote the queue. |
| `gpu.queue_list` | Current lease + queue. |
| `gpu.set_priority` | Client priority (default popcorn=100, ollama=50, agents=25). |
Model: **one holder**. Popcorn steals the GPU from Ollama / agents; the previous holder is re-queued.
**v0 limits**
- Queue and priorities are **in-memory** (lost on restart).
- `nvidia-smi` CSV parse; otherwise fake GPU `stub-gpu`.
- No real stop/restart of Ollama or GPU containers yet.
- Docker UI list: SSH if configured, otherwise placeholder.
---
## Environment variables
See `.env.example`.
| Variable | Default | Role |
|----------|---------|------|
| `MCP_TRANSPORT` | `http` | `http` or `stdio` |
| `MCP_PORT` | `8765` | Internal MCP Streamable HTTP |
| `MCP_HOST` | `0.0.0.0` | Bind address |
| `MCP_ENDPOINT` | `/mcp` | MCP path |
| `API_PORT` | `8766` | Internal REST (Hono) |
| `API_UPSTREAM` | `http://mcp:8766` | Web → API proxy target |
| `MCP_UPSTREAM` | `http://mcp:8765` | Web → MCP proxy target |
| `PUBLIC_API_URL` | _(empty)_ | Browser API base; empty = same-origin |
| `PUBLIC_BASE_URL` | _(empty)_ | Public MCP URL override |
| `ZIMAOS_API_BASE` / `TOKEN` | — | File tools |
| `ZIMAOS_SSH_*` | — | Docker tools over SSH |
---
## Build
```bash
pnpm build
pnpm --filter @zimatools/web build
```
Legacy Home Assistant integration: `legacy/ha/`.
## License
TBD. Issues: https://github.com/bobdivx/zimatools/issues
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues