feat(servers): persistent volumes + self-hosted web search and docs templates
Instances are immutable and get recreated on any server edit, so anything an MCP server wrote to its container filesystem was lost at exactly that point. That ruled out every stateful MCP server, docs-mcp among them: its index is a SQLite file (better-sqlite3 + sqlite-vec) and it has no external-database mode, so no amount of Postgres helps. A server or template can now declare volumes. The backing store is keyed on the server, not the instance — `mcpctl-<server>-<name>` — which is the whole point: an instance-scoped claim would be destroyed precisely when the data needs to survive. On Kubernetes that is a PVC ensured in the servers namespace before the pod is created and never deleted with it; on Docker, a named volume (named, not anonymous, so `removeContainer`'s `v: true` leaves it alone). Claims are ReadWriteOnce, so volumes and replicas > 1 are mutually exclusive; validation rejects that pair instead of leaving the extra replicas unschedulable. storageClassName is omitted rather than sent empty when no class is configured — to Kubernetes those mean different things. Also fixes a pre-existing bug in the same path: seedTemplates dropped `runtime`, so every PyPI-backed template seeded from YAML silently defaulted to node and would run `npx` against a package that only exists on PyPI. `unifi-network` declares `runtime: python` and had been seeding with runtime unset. Templates added, all self-hosted and none needing an API key: - duckduckgo — no backing service at all - searxng — needs a SearXNG engine (compose profile in stack/) - docs-mcp — open-source Context7/Ref alternative, uses the new volume Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BMXdb2qZbPSh8Q7XpTyjB
This commit is contained in:
56
README.md
56
README.md
@@ -743,6 +743,62 @@ mcpctl create server my-ha \
|
||||
--env-from-secret ha-secrets
|
||||
```
|
||||
|
||||
### Persistent volumes
|
||||
|
||||
Instances are immutable — any server edit recreates them — so by default
|
||||
anything an MCP server writes to its container filesystem is lost at that point.
|
||||
A server (or template) can declare volumes that outlive its instances:
|
||||
|
||||
```bash
|
||||
mcpctl create server docs \
|
||||
--from-template docs-mcp \
|
||||
--volume data:/data:20:longhorn # NAME:/mount/path[:SIZE_GB[:STORAGE_CLASS]]
|
||||
```
|
||||
|
||||
```yaml
|
||||
# ...or declaratively
|
||||
volumes:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
sizeGb: 20
|
||||
storageClass: longhorn
|
||||
```
|
||||
|
||||
The backing store is named after the **server**, not the instance —
|
||||
`mcpctl-<server>-<name>` — which is what lets the data survive. On Kubernetes
|
||||
that is a PVC created in the servers namespace on first start and left in place
|
||||
afterwards; on Docker it is a named volume. Neither is deleted when an instance
|
||||
or the server goes away, so reclaiming the space is a deliberate
|
||||
`kubectl delete pvc` / `docker volume rm`.
|
||||
|
||||
Notes:
|
||||
|
||||
- Claims are `ReadWriteOnce`, so a server with volumes is limited to one
|
||||
replica. Asking for more is rejected at validation rather than leaving the
|
||||
extra replicas unschedulable.
|
||||
- `storageClass` defaults to `MCPD_VOLUME_STORAGE_CLASS`, and is omitted
|
||||
entirely when neither is set. **Set it explicitly on any cluster with more
|
||||
than one default StorageClass**, where an omitted class binds
|
||||
nondeterministically.
|
||||
- Growing a volume is an explicit `kubectl edit pvc` (the class must allow
|
||||
expansion). mcpctl never resizes an existing claim, because most PVC fields
|
||||
are immutable after binding.
|
||||
- Backups capture the volume *declaration*, not its contents.
|
||||
|
||||
### Web search and docs lookup
|
||||
|
||||
The `duckduckgo`, `searxng` and `docs-mcp` templates give an agent web search and
|
||||
version-pinned library documentation. All are self-hosted and none needs an API key.
|
||||
`duckduckgo` needs no backing service at all:
|
||||
|
||||
```bash
|
||||
mcpctl create server websearch --from-template duckduckgo
|
||||
```
|
||||
|
||||
See [docs/web-search.md](docs/web-search.md) for the SearXNG engine setup (including
|
||||
the `json` format setting, without which every search silently returns nothing) and
|
||||
the `docs-mcp` index-persistence caveat.
|
||||
|
||||
## Gated Sessions
|
||||
|
||||
Projects using the `default` or `gate` plugin are **gated**. When Claude connects to a gated project:
|
||||
|
||||
Reference in New Issue
Block a user