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
2026-08-09 18:17:32 +01:00
|
|
|
name: duckduckgo
|
|
|
|
|
version: "1.0.0"
|
|
|
|
|
description: DuckDuckGo web search and page-to-markdown fetching — no API key, no backing service
|
|
|
|
|
packageName: "duckduckgo-mcp-server"
|
|
|
|
|
runtime: python
|
|
|
|
|
transport: STDIO
|
|
|
|
|
repositoryUrl: https://github.com/nickclyde/duckduckgo-mcp-server
|
2026-09-15 22:16:17 +01:00
|
|
|
# Readiness fetches a tiny static page instead of searching. A `search` probe is
|
|
|
|
|
# a DuckDuckGo scrape every interval (1,440/day at 60s) from the same IP agents
|
|
|
|
|
# search from, which is how that IP ends up answering CAPTCHA. This proves the
|
|
|
|
|
# process and its outbound fetch path work, not that DuckDuckGo is answering.
|
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
2026-08-09 18:17:32 +01:00
|
|
|
healthCheck:
|
2026-09-15 22:16:17 +01:00
|
|
|
tool: fetch_content
|
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
2026-08-09 18:17:32 +01:00
|
|
|
arguments:
|
2026-09-15 22:16:17 +01:00
|
|
|
url: "https://example.com"
|
|
|
|
|
max_length: 500
|
|
|
|
|
intervalSeconds: 300
|
|
|
|
|
timeoutSeconds: 20
|
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
2026-08-09 18:17:32 +01:00
|
|
|
env:
|
|
|
|
|
- name: DDG_SAFE_SEARCH
|
|
|
|
|
description: Result filtering — STRICT, MODERATE or OFF
|
|
|
|
|
required: false
|
|
|
|
|
defaultValue: "MODERATE"
|
|
|
|
|
- name: DDG_REGION
|
|
|
|
|
description: Default region/language code (e.g. us-en, pl-pl, uk-en)
|
|
|
|
|
required: false
|
|
|
|
|
- name: DDG_SEARCH_BACKEND
|
|
|
|
|
description: Fetch backend — auto, httpx or curl. `curl` survives more bot checks.
|
|
|
|
|
required: false
|
|
|
|
|
defaultValue: "auto"
|