refactor(mcp): make MCP a role topic instead of a service entry

The block sat under services but never behaved like a service. A service has
exactly one providing role, declared with provides, so the registry can map the
key to that role and consumers bind their flag to its presence. No role declares
provides: mcp. There are seventeen providers and the pairing is done by
allowed_consumers inside the block itself.

Every rule that enforces the service model therefore needed an exemption for it:
a shared-provider suppression in fourteen roles, a playwright-service-flag
suppression, and two freshly added exemptions in the dynamic-flag and
variants-static-override checks. Four independent rules, four escape hatches,
one cause. They are all gone now, together with a bond key that only the
resource budgeter reads and only for entries under services, and a wordpress
addon bridge that pointed at a service block that no longer exists.

The block moves to meta/mcp.yml. The applications loader maps meta/<name>.yml to
applications.<app>.<name> generically, so the move needs no wiring; the file
takes the same shape as meta/rbac.yml. Every config path follows, the variant
overrides are lifted out of their services map, and the vocabulary module leaves
the services package because it no longer describes one. roles_with_service
gains an explicit topic parameter rather than an implicit fallback, so nothing
silently resolves against the wrong container.

Files that are otherwise unrelated to this move are included where the committed
tree would otherwise keep pointing at a path that no longer resolves: thirteen
vars files, eleven playwright env templates and one task file.
This commit is contained in:
kevinveenbirkenbach committed 2026-08-06 20:36:00 +02:00
1 parent f4398e1c58
commit 06bc856c32
143 files changed
+1632 -660

No files matched your search

+1
View File
@@ -124,6 +124,7 @@ Every invokable role in the tested lifecycle envelope, with its upstream homepag
| [Matomo](roles/web-app-matomo/) | beta | Self-hosted, privacy-first web analytics platform for real-time visitor tracking and site metrics | [🌐](https://matomo.org/) [🎬](https://www.youtube.com/watch?v=Qc2kooLNDiU) [🛠️](roles/web-app-matomo/README.md#quick-setup) | [Backup Docker Volumes](roles/svc-bkp-volume-2-local/), [Corporate Design](roles/web-svc-css/), [Dashboard](roles/web-app-dashboard/), [MariaDB](roles/svc-db-mariadb/), [Prometheus](roles/web-app-prometheus/), [Universal Logout](roles/web-svc-logout/) |
| [Matrix](roles/web-app-matrix/) | beta | Federated, end-to-end-encrypted messaging platform with a web client and protocol bridges to external chat networks | [🌐](https://matrix.org/) [🎬](https://www.youtube.com/watch?v=3AVsNqH_-9M) [🛠️](roles/web-app-matrix/README.md#quick-setup) | [Backup Docker Volumes](roles/svc-bkp-volume-2-local/), [Corporate Design](roles/web-svc-css/), [Coturn](roles/web-svc-coturn/), [Dashboard](roles/web-app-dashboard/), [Keycloak](roles/web-app-keycloak/), [Mailu](roles/web-app-mailu/), [Matomo](roles/web-app-matomo/), [OpenLDAP](roles/svc-db-openldap/), [PostgreSQL](roles/svc-db-postgres/), [Prometheus](roles/web-app-prometheus/), [SeaweedFS](roles/web-app-seaweedfs/), [Universal Logout](roles/web-svc-logout/) |
| [Mattermost](roles/web-app-mattermost/) | beta | Open-source, self-hosted team messaging platform for real-time collaboration via channels, threads, and integrations | [🌐](https://mattermost.com/) [🎬](https://www.youtube.com/watch?v=szfZfQFUSnU) [🛠️](roles/web-app-mattermost/README.md#quick-setup) | [Backup Docker Volumes](roles/svc-bkp-volume-2-local/), [Content Delivery Network](roles/web-svc-cdn/), [Corporate Design](roles/web-svc-css/), [Dashboard](roles/web-app-dashboard/), [Keycloak](roles/web-app-keycloak/), [Mailu](roles/web-app-mailu/), [Matomo](roles/web-app-matomo/), [PostgreSQL](roles/svc-db-postgres/), [Prometheus](roles/web-app-prometheus/), [SeaweedFS](roles/web-app-seaweedfs/), [Universal Logout](roles/web-svc-logout/) |
| [MCP Adapter](roles/svc-ai-mcp-adapter/) | alpha | Reusable per-provider MCP adapter exposing a narrow allowlisted read surface | [🌐](https://modelcontextprotocol.io/) [🛠️](roles/svc-ai-mcp-adapter/README.md#quick-setup) | |
| [MediaWiki](roles/web-app-mediawiki/) | beta | Collaborative wiki platform for user-driven documentation with an extensible extension ecosystem and content management | [🌐](https://www.mediawiki.org/) [🎬](https://www.youtube.com/watch?v=-JnIjpRvgNY) [🛠️](roles/web-app-mediawiki/README.md#quick-setup) | [Backup Docker Volumes](roles/svc-bkp-volume-2-local/), [Corporate Design](roles/web-svc-css/), [Dashboard](roles/web-app-dashboard/), [Keycloak](roles/web-app-keycloak/), [Mailu](roles/web-app-mailu/), [MariaDB](roles/svc-db-mariadb/), [Matomo](roles/web-app-matomo/), [Prometheus](roles/web-app-prometheus/), [Universal Logout](roles/web-svc-logout/) |
| [Memcached](roles/svc-db-memcached/) | beta | Central in-memory caching engine with per-consumer key-prefix isolation, usable centrally or as an embedded sidecar | [🌐](https://memcached.org/) [🛠️](roles/svc-db-memcached/README.md#quick-setup) | |
| [MIG](roles/web-app-mig/) | alpha | Interactive graph visualization of all Infinito.Nexus roles and their dependencies for architecture transparency | [🛠️](roles/web-app-mig/README.md#quick-setup) | [Corporate Design](roles/web-svc-css/), [Dashboard](roles/web-app-dashboard/), [Matomo](roles/web-app-matomo/), [Prometheus](roles/web-app-prometheus/) |
+96 -38
View File
@@ -1,75 +1,133 @@
# MCP Service Block 🔌
A role with a Model Context Protocol surface declares one `mcp:` entry in `meta/services.yml`. The block carries the standard consumer flags plus MCP-specific fields. Vocabulary lives in [`utils/roles/applications/services/mcp.py`](../../../../../utils/roles/applications/services/mcp.py); the hard lint is [`tests/lint/ansible/services/test_mcp_schema.py`](../../../../../tests/lint/ansible/services/test_mcp_schema.py).
A role declares `meta/mcp.yml` when it actually serves or consumes MCP. The file is a capability descriptor, not a service entry: no single role provides MCP, so the service registry cannot pair provider and consumer, and `allowed_consumers` does it instead. Vocabulary lives in [`utils/roles/applications/mcp.py`](../../../../../utils/roles/applications/mcp.py); the hard lint is [`test_mcp_schema.py`](../../../../../tests/lint/ansible/services/test_mcp_schema.py).
## Shape
`classification` is required and is one of `native_server`, `native_client`, `native_both`, `plugin_server`, `sidecar_server`, `adapter_server`. A role that cannot serve declares no `mcp` block at all.
## Server shape
```yaml
mcp:
bond: 1
enabled: false
shared: false
direction: server # server | client | both
transport: streamable_http # streamable_http | sse
exposure: internal # internal | public
auth: bearer_token # oidc | app_password | basic_auth | bearer_token | upstream_session | none
auth_subject: user # user | service_account | administrator | none
implementation: native # native | plugin | sidecar | external
endpoint: # required for direction: server | both
service_key: baserow # names a service entry in the same file
enabled: true
shared: true
classification: native_server
direction: server # server | client | both
transport: sse # streamable_http | sse
exposure: internal # internal | public
auth: app_password # oidc | app_password | basic_auth | bearer_token | upstream_session | none
auth_subject: service_account # user | service_account | administrator | none
implementation: native # native | plugin | sidecar | adapter | external
credential:
owner: mcp-web-app-example # a dedicated non-login identity, never `administrator`
source: token_store # token_store | credentials
key: web-app-example
allowed_consumers: # exact client application ids
- web-app-openwebui
endpoint:
service_key: example # names a service entry in the same file
path: /mcp
port_key: http # resolves under that service's ports.local or ports.internal
port_key: http # resolves under that service's ports.local or ports.internal
tools:
read_only_default: true
mutating_tools_enabled: false
```
`exposure: public` additionally requires a complete `limits` block.
## Client shape
A client declares what it can present instead of what it serves:
```yaml
mcp:
classification: native_client
direction: client
implementation: native
supported_transports: [streamable_http, sse]
supported_auths: [bearer_token, app_password, upstream_session]
```
A client-only block MUST NOT declare an `endpoint`.
## Adapter shape
`implementation: adapter` requires `classification: adapter_server` and a pinned, hash-checked contract:
```yaml
adapter:
type: openapi_allowlist # see below
image: registry.example.invalid/infinito-mcp-adapter
version: 1.2.3
digest: sha256:0123456789abcdef
upstream_api_version: v1
specification_path: roles/web-app-example/files/mcp/openapi-v1.json
specification_sha256: sha256:fedcba9876543210
limits:
request_bytes: 65536
response_bytes: 1048576
timeout_seconds: 15
concurrent_requests: 4
page_size: 100
result_items: 500
stream_seconds: 300
tools:
allowlist: [example_search, example_get]
schema_sha256: sha256:abcdef0123456789
read_only_default: true
mutating_tools_enabled: false
```
`type` is one of `openapi_allowlist`, `graphql_allowlist`, `named_query`, `s3_prefix`, `prometheus_readonly`, `n8n_workflow`, `resource_readonly`. `graphql_allowlist`, `named_query`, `s3_prefix` and `resource_readonly` additionally require a non-empty `adapter.scope` naming the exact buckets, prefixes, collections or queries reachable. `openapi_allowlist` and `graphql_allowlist` require a checked-in `specification_path` under the role's own `files/`.
## Rules
- `direction` is required; all MCP-specific fields MUST be literals (only `enabled`/`shared` may carry Jinja).
- `direction: server | both` requires `auth` and a complete `endpoint` (`service_key`, `path`, `port_key`).
- `classification` is required and MUST be deployable; all MCP-specific fields MUST be literals (only `enabled`/`shared` may carry Jinja).
- `enabled`/`shared` MUST NOT name a peer role. Provider enablement is explicit operator or variant state; the variant axis lives in `meta/variants.yml`.
- `direction: server | both` requires `auth`, `credential`, `allowed_consumers` and a complete `endpoint`.
- `credential.owner` MUST NOT be `administrator`. Two providers resolving to the same owner and secret abort discovery.
- `auth: none` requires `exposure: internal`.
- `auth_subject: service_account | administrator` requires an explicit `tools.mutating_tools_enabled: false`; omitting the key is rejected, because a privileged subject must state its tool policy rather than inherit a default. The field records the intent; where upstream exposes mutating tools unconditionally, the role README names the missing enforcement.
- `auth_subject: service_account | administrator` requires an explicit `tools.mutating_tools_enabled: false`.
- `auth_subject: user` or `auth: oidc` requires a `delegation` block; see [mcp-delegation.md](mcp-delegation.md).
- `endpoint.service_key` MUST name a service in the same file; `endpoint.port_key` MUST resolve under its `ports.local` or `ports.internal`.
- Suppress a finding with `# nocheck: mcp-schema` (file head = whole file, on/above a line = that finding).
## Public exposure
`exposure: public` puts the endpoint on the role's own vhost, so it inherits
what that vhost already provides: routing through the shared proxy, TLS from the
platform certificate, `client_max_body_size` from `server.client_max_body_size`,
and the proxy read timeout of the location it is mounted in.
`exposure: public` puts the endpoint on the role's own vhost, so it inherits what that vhost already provides: routing through the shared proxy, TLS from the platform certificate, `client_max_body_size` from `server.client_max_body_size`, and the proxy read timeout of the location it is mounted in.
Rate limiting is not among them. The proxy ships no `limit_req` or `limit_conn`
anywhere, so a public MCP endpoint is only as protected as its own
authentication. An operator exposing one to the open internet has to put a rate
limiter in front of the vhost; the platform will not supply one.
Rate limiting is not among them. The proxy ships no `limit_req` or `limit_conn` anywhere, so a public MCP endpoint is only as protected as its own authentication and its declared `limits`. An operator exposing one to the open internet has to put a rate limiter in front of the vhost; the platform will not supply one.
Prefer `exposure: internal`. A client inside the container network reaches the
endpoint without any of this, which is why five of the eight server roles stay
internal and only those whose upstream mounts MCP under the application's own
public routes are public.
Prefer `exposure: internal`. A client inside the container network reaches the endpoint without any of this.
## Why stdio is not deployed
`transport` accepts `streamable_http` and `sse` only. A stdio MCP server is a
local process the client spawns, so it inherits the client container's
filesystem and network with no endpoint to authenticate, no proxy to route
through, and nothing for a probe to reach. Every property the other rules rely
on (`auth`, `exposure`, `endpoint`) is undefined for it. Clients that support
stdio are pinned away from it in their own role: Flowise sets
`CUSTOM_MCP_PROTOCOL=sse` so no flow can spawn a command.
`transport` accepts `streamable_http` and `sse` only. A stdio MCP server is a local process the client spawns, so it inherits the client container's filesystem and network with no endpoint to authenticate, no proxy to route through, and nothing for a probe to reach. Every property the other rules rely on (`auth`, `exposure`, `endpoint`) is undefined for it. Clients that support stdio are pinned away from it in their own role: Flowise sets `CUSTOM_MCP_PROTOCOL=sse` so no flow can spawn a command.
## Client discovery
Client roles discover server-capable roles through the `roles_with_service` lookup with the `direction` kwarg:
Clients do not read the server list directly. [`lookup('mcp_servers')`](../../../../../plugins/lookup/mcp_servers.py) computes the intersection of the provider's `allowed_consumers` with the calling client's `supported_transports` and `supported_auths`, resolves each provider's declared credential, and returns:
```yaml
{{ lookup('roles_with_service', 'mcp', direction='server') }}
MCP_DISCOVERY: "{{ lookup('mcp_servers') }}"
MCP_DISCOVERED_SERVERS: "{{ MCP_DISCOVERY.selected }}"
MCP_REJECTED_SERVERS: "{{ MCP_DISCOVERY.rejected }}"
```
Each returned entry carries `id`, `canonical_domain`, `canonical_url`, `iframe`, plus `transport`, `auth`, `auth_subject` and `endpoint` (`service_key`, `path`, resolved `port`). Callers that omit `direction` keep the original 4-key entries.
Every rejected entry carries a stable `reason`: `consumer_not_allowed`, `transport_unsupported`, `auth_unsupported`, `credential_missing` or `endpoint_unreachable`.
## RBAC
Reaching a tool server is a distinct grant from using the application, carried by the provider's `mcp` RBAC role. `mcp` is application-scoped: it is granted through `application_roles`, never through the unscoped `roles` list, so granting it on one application grants it nowhere else.
```yaml
users:
alice:
application_roles:
web-app-baserow: [mcp]
```
## See Also
- Service metadata basics: [base.md](base.md)
- User delegation audit: [mcp-delegation.md](mcp-delegation.md)
+11 -7
View File
@@ -18,7 +18,7 @@ cannot be rendered, so they abort the run instead of disappearing quietly: a
client that silently ends up with fewer tools than the deployment declared is
indistinguishable from one that works.
The provider credential is whatever ``services.mcp.credential`` declares:
The provider credential is whatever ``mcp.credential`` declares:
``owner`` names the principal, ``source`` where its secret lives
(``token_store`` or the role's own ``credentials``), ``key`` the entry. No
provider inherits the administrator's token.
@@ -32,7 +32,7 @@ from ansible.errors import AnsibleError
from ansible.plugins.loader import lookup_loader
from ansible.plugins.lookup import LookupBase
from utils.roles.applications.services.mcp import DEFAULT_MCP_TRANSPORT
from utils.roles.applications.mcp import DEFAULT_MCP_TRANSPORT
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
@@ -113,7 +113,7 @@ def build_mcp_discovery(
Args:
servers: ``roles_with_service('mcp', direction='server')`` entries.
consumer_id: ``application_id`` of the client doing the discovery.
consumer: the client's own ``services.mcp`` block, carrying
consumer: the client's own ``mcp`` block, carrying
``supported_transports`` and ``supported_auths``.
credentials: resolved ``(token, owner)`` per provider role id.
path_keys: resolved ``key_credential`` values, keyed by role id.
@@ -253,7 +253,13 @@ class LookupModule(LookupBase):
servers = lookup_loader.get(
"roles_with_service", loader=self._loader, templar=templar
).run(["mcp"], variables=vars_, direction="server", scope="deployment")[0]
).run(
["mcp"],
variables=vars_,
topic="mcp",
direction="server",
scope="deployment",
)[0]
applications = lookup_loader.get(
"applications", loader=self._loader, templar=templar
@@ -262,9 +268,7 @@ class LookupModule(LookupBase):
[], variables=vars_
)[0]
consumer = ((applications.get(consumer_id) or {}).get("services") or {}).get(
"mcp"
) or {}
consumer = (applications.get(consumer_id) or {}).get("mcp") or {}
credentials: dict[str, tuple[str, str]] = {}
path_keys: dict[str, str] = {}
+4 -3
View File
@@ -45,7 +45,7 @@ from ansible.plugins.loader import lookup_loader
from ansible.plugins.lookup import LookupBase
from utils.domains.primary_domain import get_primary_domain
from utils.roles.applications.services.mcp import DEFAULT_MCP_TRANSPORT
from utils.roles.applications.mcp import DEFAULT_MCP_TRANSPORT
from utils.roles.entity.name import get_entity_name
if TYPE_CHECKING:
@@ -125,6 +125,7 @@ class LookupModule(LookupBase):
).run([], variables=vars_)[0]
scope = str(kwargs.get("scope", "host")).strip().lower()
topic = kwargs.get("topic")
direction_raw = kwargs.get("direction")
direction = (
str(direction_raw).strip().lower() if direction_raw is not None else None
@@ -141,8 +142,8 @@ class LookupModule(LookupBase):
continue
services = app_config.get("services")
if not isinstance(services, dict):
continue
block = services.get(service_name)
services = {}
block = app_config.get(topic) if topic else services.get(service_name)
if not isinstance(block, dict):
continue
if not bool(block.get("enabled")):
+1
View File
@@ -23,6 +23,7 @@ flowchart LR
end
subgraph role [svc-db-qdrant 🐳🐝]
svc_qdrant["qdrant"]
svc_qdrantmcp["qdrantmcp"]
svc_container_backup["container_backup"]
end
subgraph dependents [Dependents]
+47
View File
@@ -0,0 +1,47 @@
---
enabled: false
shared: false
classification: adapter_server
direction: server
source_url: https://s.infinito.nexus/code
supported_version: v1.18.2
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: service_account
implementation: adapter
credential:
owner: mcp-svc-db-qdrant
source: credentials
key: mcp_bearer
allowed_consumers:
- web-app-openwebui
- web-app-hermes
- web-app-openclaw
endpoint:
service_key: qdrantmcp
path: /mcp
port_key: http
adapter:
type: openapi_allowlist
image: infinito/mcp-adapter
version: 1.0.0
digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
upstream_api_version: v1
specification_path: roles/svc-db-qdrant/files/mcp/tools.json
specification_sha256: sha256:95037a1f1d060f8bbf092ac5943fb705d699ce0c30491a4b1c884fe1bf81d2f7
limits:
request_bytes: 65536
response_bytes: 1048576
timeout_seconds: 15
concurrent_requests: 4
page_size: 100
result_items: 100
stream_seconds: 300
tools:
allowlist:
- qdrant_list_collections
- qdrant_get_collection
schema_sha256: sha256:95037a1f1d060f8bbf092ac5943fb705d699ce0c30491a4b1c884fe1bf81d2f7
read_only_default: true
mutating_tools_enabled: false
+18
View File
@@ -26,6 +26,24 @@ qdrant:
internal:
qdrant: 6333
lifecycle: beta
# nocheck: playwright-service-flag internal adapter sidecar, covered by the role's MCP integration test
# nocheck: dockerfile-custom the Dockerfile is owned by svc-ai-mcp-adapter and staged per instance, so every adapter-backed provider shares one audited build
# nocheck: dynamic-flag the sidecar tracks this role's own mcp flag, not another role's presence; a group_names expression would decide its exposure from an unrelated deployment
qdrantmcp:
enabled: "{{ lookup('config', 'svc-db-qdrant', 'mcp.enabled') | bool }}"
shared: false
image: python
# nocheck: docker-version base of the self-built stdlib adapter, not the served application
version: "3.13-slim"
custom: true
name: qdrantmcp
cpus: "0.25"
mem_reservation: 24m
mem_limit: 48m
pids_limit: 64
ports:
internal:
http: 8080
# nocheck: playwright-service-flag, dynamic-flag infrastructure backup consumer
container_backup: # nocheck: bond-baseline backup host joins outside the variant topology
+12
View File
@@ -0,0 +1,12 @@
---
# Matrix-deploy variant overrides for svc-db-qdrant.
# See docs/contributing/artefact/files/role/variants.md for the file format.
# variant 0: MCP adapter enabled (canonical default).
- mcp:
enabled: true
shared: true
- mcp:
enabled: false
shared: false
+4 -7
View File
@@ -22,7 +22,6 @@ flowchart LR
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_mailu["web-app-mailu 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_openwebui["web-app-openwebui 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_app_seaweedfs["web-app-seaweedfs 🐳🐝"]
dep_web_svc_css["web-svc-css 💻"]
@@ -33,7 +32,6 @@ flowchart LR
svc_dashboard["dashboard"]
svc_matomo["matomo"]
svc_email["email"]
svc_mcp["mcp"]
svc_sso["sso"]
svc_redis["redis"]
svc_postgres["postgres"]
@@ -56,7 +54,6 @@ flowchart LR
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_mailu -. "0..1" .-> svc_email
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_openwebui -. "0..1" .-> svc_mcp
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_app_seaweedfs -. "0..1" .-> svc_seaweedfs
dep_web_svc_css -. "0..1" .-> svc_css
@@ -148,7 +145,7 @@ Configuration is controlled via `applications.<app>.bootstrap_admin.*`:
## MCP Server
Baserow ships a native MCP server in the OSS backend, mounted on the ASGI root
ahead of Django. The role turns it on with `services.mcp.enabled` and hands
ahead of Django. The role turns it on with `mcp.enabled` and hands
clients the container-network origin (`exposure: internal`); `env.j2` then adds
that origin to `BASEROW_EXTRA_PUBLIC_URLS` so the image's Caddy layer routes
`/mcp/*` to the backend instead of falling through to the Nuxt frontend.
@@ -190,7 +187,7 @@ Read tools: `list_databases`, `list_tables`, `get_table_schema`,
Write tools: `create_rows`, `update_rows`, `delete_rows`. Baserow 2.3.1 exposes
these unconditionally; the image carries no env var, setting or UI toggle that
removes them. `services.mcp.tools.mutating_tools_enabled` is declared `false` as
removes them. `mcp.tools.mutating_tools_enabled` is declared `false` as
the deployment's intent, not as an enforced state: Baserow serves the three write
tools regardless. What bounds them is the endpoint owner, whose reach is the one
workspace that account owns.
@@ -209,12 +206,12 @@ the role's `mcp` RBAC group, not on being signed in.
### Default state
Off. `services.mcp.enabled` is true only while `web-app-openwebui` is part of the
Off. `mcp.enabled` is true only while `web-app-openwebui` is part of the
deployment.
### How to disable
Remove the MCP client role, or pin `services.mcp.enabled: false` for this role in
Remove the MCP client role, or pin `mcp.enabled: false` for this role in
the inventory. The endpoint is then not provisioned and `BASEROW_EXTRA_PUBLIC_URLS`
drops the MCP origin, so the image's Caddy layer stops routing `/mcp/*`.
+39
View File
@@ -0,0 +1,39 @@
---
enabled: true
shared: true
classification: native_server
direction: server
source_url: https://baserow.io/user-docs/mcp-server # nocheck: url — page is live (HEAD+GET 200); baserow.io edge intermittently 404s CI runner IPs
supported_version: 2.3.1
minimum_version: "1.30"
transport: sse
exposure: internal
auth: app_password
auth_subject: service_account
implementation: native
credential:
owner: mcp-web-app-baserow
source: token_store
key: web-app-baserow
allowed_consumers:
- web-app-openwebui
- web-app-hermes
- web-app-openclaw
- web-app-n8n
endpoint:
service_key: baserow
path: /mcp
port_key: http
key_credential: mcp_endpoint_key
suffix: sse
tools:
allowlist:
- create_rows
- delete_rows
- get_table_schema
- list_databases
- list_table_rows
- list_tables
- update_rows
read_only_default: true
mutating_tools_enabled: false
-23
View File
@@ -15,29 +15,6 @@ email:
bond: 1
enabled: "{{ 'web-app-mailu' in group_names }}"
shared: "{{ 'web-app-mailu' in group_names }}"
# nocheck: shared-provider
mcp:
bond: 1
enabled: "{{ 'web-app-openwebui' in group_names }}"
shared: "{{ 'web-app-openwebui' in group_names }}"
direction: server
source_url: https://baserow.io/user-docs/mcp-server # nocheck: url — page is live (HEAD+GET 200); baserow.io edge intermittently 404s CI runner IPs
minimum_version: "1.30"
notes: Native built-in MCP server.
transport: sse
exposure: internal
auth: app_password
auth_subject: service_account
implementation: native
endpoint:
service_key: baserow
path: /mcp
port_key: http
key_credential: mcp_endpoint_key
suffix: sse
tools:
read_only_default: true
mutating_tools_enabled: false
sso:
bond: 1
enabled: "{{ 'web-app-keycloak' in group_names }}"
+6 -6
View File
@@ -19,9 +19,6 @@
matomo:
enabled: true
shared: true
mcp:
enabled: true
shared: true
sso:
enabled: true
shared: true
@@ -38,6 +35,9 @@
css:
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -54,9 +54,6 @@
matomo:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
sso:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
@@ -73,3 +70,6 @@
css:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
@@ -21,7 +21,7 @@ MATOMO_BASE_URL={{ lookup('tls', 'web-app-matomo', 'url.base') | dotenv_quote }}
MATOMO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.matomo.enabled') | string | lower }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'services.mcp.enabled') | string | lower }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'mcp.enabled') | string | lower }}
NODE_TLS_REJECT_UNAUTHORIZED=0
+2 -2
View File
@@ -33,9 +33,9 @@ BASEROW_MANAGE_PY: "/baserow/backend/src/baserow/manage.py"
BASEROW_SERVICE_NAME: "baserow"
BASEROW_INTERNAL_URL: "http://{{ BASEROW_CONTAINER }}:{{ lookup('config', application_id, 'services.baserow.ports.internal.http') }}"
BASEROW_MCP_ENABLED: "{{ lookup('config', application_id, 'services.mcp.enabled') }}"
BASEROW_MCP_ENABLED: "{{ lookup('config', application_id, 'mcp.enabled') }}"
BASEROW_MCP_ENDPOINT_KEY: "{{ lookup('config', application_id, 'credentials.mcp_endpoint_key') }}"
BASEROW_MCP_ENDPOINT_PATH: "{{ lookup('config', application_id, 'services.mcp.endpoint.path') }}"
BASEROW_MCP_ENDPOINT_PATH: "{{ lookup('config', application_id, 'mcp.endpoint.path') }}"
BASEROW_MCP_ENDPOINT_NAME: "Infinito.Nexus"
BASEROW_MCP_WORKSPACE_NAME: "Infinito.Nexus MCP"
BASEROW_MCP_OWNER_EMAIL: "mcp@{{ lookup('domain', application_id) }}"
+1
View File
@@ -37,6 +37,7 @@ flowchart LR
svc_prometheus["prometheus"]
svc_checkmk["checkmk"]
svc_container_backup["container_backup"]
svc_checkmkmcp["checkmkmcp"]
end
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_svc_db_openldap -. "0..1" .-> svc_ldap
+48
View File
@@ -0,0 +1,48 @@
---
enabled: false
shared: false
classification: adapter_server
direction: server
source_url: https://s.infinito.nexus/code
supported_version: 1.0.0
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: service_account
implementation: adapter
credential:
owner: mcp-web-app-checkmk
source: credentials
key: mcp_bearer
allowed_consumers:
- web-app-openwebui
- web-app-hermes
- web-app-openclaw
endpoint:
service_key: checkmkmcp
path: /mcp
port_key: http
adapter:
type: openapi_allowlist
image: infinito/mcp-adapter
version: 1.0.0
digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
upstream_api_version: v1
specification_path: roles/web-app-checkmk/files/mcp/tools.json
specification_sha256: sha256:9a769b4524a2c55a12e06c6fb3d906cff1ccd7f1f3445e3543a94891a8d466be
limits:
request_bytes: 65536
response_bytes: 1048576
timeout_seconds: 15
concurrent_requests: 4
page_size: 100
result_items: 100
stream_seconds: 300
tools:
allowlist:
- checkmk_get_host_status
- checkmk_list_hosts
- checkmk_list_services
schema_sha256: sha256:9a769b4524a2c55a12e06c6fb3d906cff1ccd7f1f3445e3543a94891a8d466be
read_only_default: true
mutating_tools_enabled: false
+15
View File
@@ -88,3 +88,18 @@ container_backup: # nocheck: bond-baseline backup host joins outside the variant
bond: 1
enabled: "{{ 'svc-bkp-volume-2-local' in group_names }}"
shared: true
# nocheck: playwright-service-flag internal adapter sidecar, covered by the role's MCP guest spec
# nocheck: dockerfile-custom the Dockerfile is owned by svc-ai-mcp-adapter and staged per instance, so every adapter-backed provider shares one audited build
checkmkmcp:
image: python
# nocheck: docker-version base of the self-built stdlib adapter, not the served application
version: "3.13-slim"
custom: true
name: checkmkmcp
cpus: "0.25"
mem_reservation: 16m
mem_limit: 24m
pids_limit: 64
ports:
internal:
http: 8080
+9
View File
@@ -27,6 +27,9 @@
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -56,6 +59,9 @@
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
- services:
container_backup:
enabled: false
@@ -84,3 +90,6 @@
sso:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
-1
View File
@@ -36,7 +36,6 @@ flowchart LR
svc_css["css"]
svc_prometheus["prometheus"]
svc_container_backup["container_backup"]
svc_mcp["mcp ❌"]
end
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_svc_db_postgres -. "0..1" .-> svc_postgres
@@ -69,11 +69,3 @@ container_backup: # nocheck: bond-baseline backup host joins outside the variant
bond: 1
enabled: "{{ 'svc-bkp-volume-2-local' in group_names }}"
shared: true
# nocheck: dynamic-flag a blocker block can never be enabled, so a group_names expression would be dead code
mcp:
enabled: false
shared: false
blocker: hosted_only
source_url: https://support.atlassian.com/atlassian-rovo-mcp-server/docs/getting-started-with-the-atlassian-remote-mcp-server/
notes: Rovo MCP is Atlassian Cloud only; this role deploys an unsupported self-hosted container.
-1
View File
@@ -42,7 +42,6 @@ flowchart LR
svc_css["css"]
svc_prometheus["prometheus"]
svc_container_backup["container_backup"]
svc_mcp["mcp ❌"]
end
subgraph dependents [Dependents]
dpt_web_app_nextcloud["web-app-nextcloud 🐳🐝"]
@@ -104,12 +104,3 @@ container_backup: # nocheck: bond-baseline backup host joins outside the variant
bond: 1
enabled: "{{ 'svc-bkp-volume-2-local' in group_names }}"
shared: true
# nocheck: dynamic-flag a blocker block can never be enabled, so a group_names expression would be dead code
# nocheck: playwright-service-flag no MCP surface is served, so no spec can exercise one
mcp:
enabled: false
shared: false
blocker: hosted_only
source_url: https://meta.discourse.org/
notes: An MCP repository exists, but no stable supported server contract has been confirmed.
+1
View File
@@ -48,6 +48,7 @@ flowchart LR
svc_javascript["javascript"]
svc_prometheus["prometheus"]
svc_seaweedfs["seaweedfs"]
svc_fidermcp["fidermcp"]
end
dep_svc_db_postgres -. "0..1" .-> svc_postgres
dep_svc_db_redis -. "0..1" .-> svc_redis
+48
View File
@@ -0,0 +1,48 @@
---
enabled: false
shared: false
classification: adapter_server
direction: server
source_url: https://s.infinito.nexus/code
supported_version: 1.0.0
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: service_account
implementation: adapter
credential:
owner: mcp-web-app-fider
source: credentials
key: mcp_bearer
allowed_consumers:
- web-app-openwebui
- web-app-hermes
- web-app-openclaw
endpoint:
service_key: fidermcp
path: /mcp
port_key: http
adapter:
type: openapi_allowlist
image: infinito/mcp-adapter
version: 1.0.0
digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
upstream_api_version: v1
specification_path: roles/web-app-fider/files/mcp/tools.json
specification_sha256: sha256:50c9683dcad2b66b3a1c07b87fb553bdf5543103cfccb75522374362528fb524
limits:
request_bytes: 65536
response_bytes: 1048576
timeout_seconds: 15
concurrent_requests: 4
page_size: 100
result_items: 100
stream_seconds: 300
tools:
allowlist:
- fider_get_suggestion
- fider_list_tags
- fider_search_suggestions
schema_sha256: sha256:50c9683dcad2b66b3a1c07b87fb553bdf5543103cfccb75522374362528fb524
read_only_default: true
mutating_tools_enabled: false
+15
View File
@@ -74,3 +74,18 @@ seaweedfs:
bond: 1
enabled: "{{ 'web-app-seaweedfs' in group_names }}"
shared: "{{ 'web-app-seaweedfs' in group_names }}"
# nocheck: playwright-service-flag internal adapter sidecar, covered by the role's MCP guest spec
# nocheck: dockerfile-custom the Dockerfile is owned by svc-ai-mcp-adapter and staged per instance, so every adapter-backed provider shares one audited build
fidermcp:
image: python
# nocheck: docker-version base of the self-built stdlib adapter, not the served application
version: "3.13-slim"
custom: true
name: fidermcp
cpus: "0.25"
mem_reservation: 16m
mem_limit: 24m
pids_limit: 64
ports:
internal:
http: 8080
+6
View File
@@ -33,6 +33,9 @@
seaweedfs:
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
dashboard:
enabled: false
@@ -62,3 +65,6 @@
seaweedfs:
enabled: false
shared: false
mcp:
enabled: false
shared: false
+2 -5
View File
@@ -39,7 +39,6 @@ flowchart LR
svc_sso["sso"]
svc_ldap["ldap ❌"]
svc_litellm["litellm"]
svc_mcp["mcp"]
svc_qdrant["qdrant"]
svc_postgres["postgres"]
svc_flowise["flowise"]
@@ -61,10 +60,8 @@ flowchart LR
dep_svc_db_qdrant -. "0..1" .-> svc_qdrant
dep_svc_db_redis -. "0..1" .-> svc_redis
dep_web_app_baserow -. "0..1" .-> svc_baserow
dep_web_app_baserow -. "0..1" .-> svc_mcp
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_homeassistant -. "0..1" .-> svc_homeassistant
dep_web_app_homeassistant -. "0..1" .-> svc_mcp
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_mailu -. "0..1" .-> svc_email
dep_web_app_matomo -. "0..1" .-> svc_matomo
@@ -157,12 +154,12 @@ is a deliberate operator step.
### Default state
Off. `services.mcp.enabled` is false unless an MCP server role is part of the
Off. `mcp.enabled` is false unless an MCP server role is part of the
deployment.
### How to disable
Remove the MCP server roles, or pin `services.mcp.enabled: false` for this role.
Remove the MCP server roles, or pin `mcp.enabled: false` for this role.
The security-check and deny-list overrides are then not rendered.
## Credits
+18
View File
@@ -0,0 +1,18 @@
---
enabled: true
shared: true
classification: native_client
direction: client
source_url: https://github.com/FlowiseAI/Flowise/tree/flowise%403.1.4/packages/server/src/routes/custom-mcp-servers
supported_version: 3.1.4
minimum_version: 3.0.0
implementation: native
supported_transports:
- streamable_http
supported_auths:
- bearer_token
- app_password
- upstream_session
tools:
read_only_default: true
mutating_tools_enabled: false
+8 -20
View File
@@ -32,23 +32,6 @@ litellm:
bond: 1
enabled: "{{ 'svc-ai-litellm' in group_names }}"
shared: "{{ 'svc-ai-litellm' in group_names }}"
mcp:
bond: 1
enabled: "{{ 'web-app-homeassistant' in group_names or 'web-app-baserow' in group_names }}"
shared: "{{ 'web-app-homeassistant' in group_names or 'web-app-baserow' in group_names }}"
direction: client
source_url: https://docs.flowiseai.com/tutorials/tools-and-mcp
minimum_version: 3.0.0
notes: Custom MCP tool node, configured per flow; no instance-level registry API, so servers cannot be preregistered. Arbitrary stdio commands stay off in
deployments.
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: user
implementation: native
tools:
read_only_default: true
mutating_tools_enabled: false
# nocheck: playwright-service-flag - DB engine, covered by role-local integration tests
qdrant:
bond: 1
@@ -71,10 +54,15 @@ flowise:
shared: true
backup:
no_stop_required: false
image: flowiseai/flowise
version: 3.1.3
image: node
# nocheck: docker-version
version: "24-bookworm-slim"
npm_version: 3.1.4
sqlite_version: 12.11.1
custom: true
name: flowise
min_storage: 1195MB
# nocheck: min-storage-headroom Reason: the deployed image is built here from node:24-bookworm-slim and measures 4.8 GB, so the base image transfer is not its footprint
min_storage: 6GB
cpus: "1.0"
mem_reservation: 1g
mem_limit: 2g
+6 -6
View File
@@ -29,9 +29,6 @@
litellm:
enabled: true
shared: true
mcp:
enabled: true
shared: true
qdrant:
enabled: true
shared: true
@@ -51,6 +48,9 @@
baserow:
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -76,9 +76,6 @@
litellm:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
qdrant:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
@@ -98,3 +95,6 @@
baserow:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
+21 -1
View File
@@ -5,6 +5,10 @@ http_port: "{{ lookup('config', application_id, 'services.flowise.ports.local.ht
FLOWISE_CONTAINER: "{{ lookup('config', application_id, 'services.flowise.name') }}"
FLOWISE_PORT_INTERNAL: 3000
FLOWISE_NODE_MODULES: /opt/flowise/node_modules
FLOWISE_BUILD_DIR: "{{ [lookup('container', application_id, 'directories.instance'), 'build'] | path_join }}"
FLOWISE_NPM_VERSION: "{{ lookup('config', application_id, 'services.flowise.npm_version') }}"
FLOWISE_SQLITE_VERSION: "{{ lookup('config', application_id, 'services.flowise.sqlite_version') }}"
FLOWISE_LDAP_ENABLED: "{{ lookup('config', application_id, 'services.ldap.enabled') }}"
@@ -13,7 +17,7 @@ FLOWISE_LITELLM_API_KEY: "{{ lookup('config', application_id, 'credentials.litel
FLOWISE_QDRANT_INTERNAL_URL: "{{ lookup('engine', 'qdrant', application_id, 'url') }}"
FLOWISE_MCP_ENABLED: "{{ lookup('config', application_id, 'services.mcp.enabled') }}"
FLOWISE_MCP_ENABLED: "{{ lookup('config', application_id, 'mcp.enabled') }}"
FLOWISE_MCP_SERVERS: >-
{%- set out = [] -%}
{%- if FLOWISE_MCP_ENABLED | bool -%}
@@ -22,3 +26,19 @@ FLOWISE_MCP_SERVERS: >-
{%- endfor -%}
{%- endif -%}
{{ out }}
FLOWISE_API_BOT_USER: "{{ lookup('users', 'flowise-api-bot') }}"
FLOWISE_API_BOT_PASSWORD: "{{ lookup('config', application_id, 'credentials.api_bot_password') }}"
FLOWISE_MCP_API_KEY_NAME: "infinito:mcp-registry"
FLOWISE_MCP_DESIRED: >-
{%- set out = [] -%}
{%- for server in FLOWISE_MCP_SERVERS -%}
{%- set _ = out.append({
'id': server.id,
'url': server.url,
'transport': server.transport | replace('-', '_'),
'header': 'Authorization',
'token': server | mcp_authorization,
'tools': []}) -%}
{%- endfor -%}
{{ out }}
+2 -7
View File
@@ -20,11 +20,9 @@ flowchart LR
dep_svc_db_openldap["svc-db-openldap 🐳🐝"]
dep_svc_db_redis["svc-db-redis 🐳🐝"]
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
dep_web_app_hermes["web-app-hermes 🐳🐝"]
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_mailu["web-app-mailu 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_openclaw["web-app-openclaw 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_app_seaweedfs["web-app-seaweedfs 🐳🐝"]
dep_web_svc_css["web-svc-css 💻"]
@@ -40,7 +38,6 @@ flowchart LR
svc_mariadb["mariadb"]
svc_gitea["gitea"]
svc_giteamcp["giteamcp"]
svc_mcp["mcp"]
svc_redis["redis"]
svc_minio["minio ❌"]
svc_seaweedfs["seaweedfs"]
@@ -53,11 +50,9 @@ flowchart LR
dep_svc_db_openldap -. "0..1" .-> svc_ldap
dep_svc_db_redis -. "0..1" .-> svc_redis
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_hermes -. "0..1" .-> svc_mcp
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_mailu -. "0..1" .-> svc_email
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_openclaw -. "0..1" .-> svc_mcp
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_app_seaweedfs -. "0..1" .-> svc_seaweedfs
dep_web_svc_css -. "0..1" .-> svc_css
@@ -155,7 +150,7 @@ repository, issue, pull request, search, file, branch, tag, commit and release.
### Default state
Off. `services.mcp.enabled` is false unless `web-app-hermes` or
Off. `mcp.enabled` is false unless `web-app-hermes` or
`web-app-openclaw` is deployed alongside.
### Public vhost
@@ -166,7 +161,7 @@ vhost is answered with an SSO redirect, never with an MCP response.
### How to disable
Remove the MCP client roles from the deployment, or pin
`services.mcp.enabled: false` for this role. The `giteamcp` service is then not
`mcp.enabled: false` for this role. The `giteamcp` service is then not
rendered into the compose file.
## Credits
+52
View File
@@ -0,0 +1,52 @@
---
enabled: true
shared: true
classification: sidecar_server
direction: server
source_url: https://pkg.go.dev/gitea.com/gitea/gitea-mcp
minimum_version: "1.24"
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: service_account
implementation: sidecar
credential:
owner: mcp-web-app-gitea
source: token_store
key: web-app-gitea
allowed_consumers:
- web-app-openwebui
- web-app-hermes
- web-app-openclaw
endpoint:
service_key: giteamcp
path: /mcp
port_key: http
tools:
allowlist:
- attachment_read
- get_commit
- get_dir_contents
- get_file_contents
- get_latest_release
- get_me
- get_release
- get_repository_tree
- get_tag
- get_user_orgs
- issue_read
- list_branches
- list_commits
- list_issues
- list_my_repos
- list_org_repos
- list_pull_requests
- list_releases
- list_tags
- pull_request_read
- search_issues
- search_org_teams
- search_repos
- search_users
read_only_default: true
mutating_tools_enabled: false
-20
View File
@@ -93,26 +93,6 @@ giteamcp:
ports:
internal:
http: 8080
mcp:
bond: 1
enabled: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names }}"
shared: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names }}"
direction: server
source_url: https://pkg.go.dev/gitea.com/gitea/gitea-mcp
minimum_version: "1.24"
notes: Project-owned package; packaging and auth maturity gate it.
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: administrator
implementation: sidecar
endpoint:
service_key: giteamcp
path: /mcp
port_key: http
tools:
read_only_default: true
mutating_tools_enabled: false
# nocheck: dynamic-flag
# nocheck: playwright-service-flag — DB engine, covered by role-local integration tests
redis:
+9 -9
View File
@@ -26,9 +26,6 @@
matomo:
enabled: true
shared: true
mcp:
enabled: true
shared: true
sso:
enabled: true
shared: true
@@ -41,6 +38,9 @@
css:
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -64,9 +64,6 @@
matomo:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
sso:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
@@ -79,6 +76,9 @@
css:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
- services:
container_backup:
enabled: false
@@ -104,12 +104,12 @@
matomo:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
prometheus:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
seaweedfs:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
@@ -24,7 +24,7 @@ MATOMO_BASE_URL={{ lookup('tls', 'web-app-matomo', 'url.base') | dotenv_quote }}
MATOMO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.matomo.enabled') | string | lower }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'services.mcp.enabled') | string | lower }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'mcp.enabled') | string | lower }}
PERSONA_ADMINISTRATOR_BLOCKED=true
+3 -3
View File
@@ -29,11 +29,11 @@ GITEA_REDIS_PASSWORD: "{{ lookup('engine', 'redis', application_id, 'password')
GITEA_INTERNAL_API_URL: "http://{{ entity_name }}:{{ lookup('config', application_id, 'services.gitea.ports.internal.http') }}/api/v1"
GITEA_MCP_ENABLED: "{{ lookup('config', application_id, 'services.mcp.enabled') }}"
GITEA_MCP_SERVICE_KEY: "{{ lookup('config', application_id, 'services.mcp.endpoint.service_key') }}"
GITEA_MCP_ENABLED: "{{ lookup('config', application_id, 'mcp.enabled') }}"
GITEA_MCP_SERVICE_KEY: "{{ lookup('config', application_id, 'mcp.endpoint.service_key') }}"
GITEA_MCP_CONTAINER: "{{ lookup('config', application_id, 'services.giteamcp.name') }}"
GITEA_MCP_PORT: "{{ lookup('config', application_id, 'services.giteamcp.ports.internal.http') }}"
GITEA_MCP_URL: "http://{{ GITEA_MCP_SERVICE_KEY }}:{{ GITEA_MCP_PORT }}{{ lookup('config', application_id, 'services.mcp.endpoint.path') }}"
GITEA_MCP_URL: "http://{{ GITEA_MCP_SERVICE_KEY }}:{{ GITEA_MCP_PORT }}{{ lookup('config', application_id, 'mcp.endpoint.path') }}"
GITEA_MCP_UPSTREAM_URL: "http://{{ entity_name }}:{{ lookup('config', application_id, 'services.gitea.ports.internal.http') }}"
GITEA_MCP_TOKEN_NAME: "gitea-mcp"
GITEA_MCP_TOKEN_SCOPES: "read:repository,read:issue,read:user,read:organization,read:notification"
+5 -12
View File
@@ -19,12 +19,9 @@ flowchart LR
dep_svc_db_postgres["svc-db-postgres 🐳🐝"]
dep_svc_db_redis["svc-db-redis 🐳🐝"]
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
dep_web_app_hermes["web-app-hermes 🐳🐝"]
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_mailu["web-app-mailu 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_openclaw["web-app-openclaw 🐳🐝"]
dep_web_app_openwebui["web-app-openwebui 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_app_seaweedfs["web-app-seaweedfs 🐳🐝"]
dep_web_svc_css["web-svc-css 💻"]
@@ -36,7 +33,6 @@ flowchart LR
svc_dashboard["dashboard"]
svc_matomo["matomo"]
svc_email["email"]
svc_mcp["mcp"]
svc_redis["redis"]
svc_postgres["postgres"]
svc_gitlab["gitlab"]
@@ -58,12 +54,9 @@ flowchart LR
dep_svc_db_postgres -. "0..1" .-> svc_postgres
dep_svc_db_redis -. "0..1" .-> svc_redis
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_hermes -. "0..1" .-> svc_mcp
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_mailu -. "0..1" .-> svc_email
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_openclaw -. "0..1" .-> svc_mcp
dep_web_app_openwebui -. "0..1" .-> svc_mcp
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_app_seaweedfs -. "0..1" .-> svc_seaweedfs
dep_web_svc_css -. "0..1" .-> svc_css
@@ -84,7 +77,7 @@ Solid `1:1` edges are fixed relationships; dashed `0..1` edges are conditional (
## MCP server
`services.mcp` declares the Model Context Protocol surface GitLab serves natively from its Rails API.
`mcp` declares the Model Context Protocol surface GitLab serves natively from its Rails API.
| Property | Value |
| --- | --- |
@@ -92,13 +85,13 @@ Solid `1:1` edges are fixed relationships; dashed `0..1` edges are conditional (
| Transport | streamable HTTP (JSON-RPC `initialize`, `tools/list`, `tools/call`) |
| Auth | `Authorization: Bearer <token>` |
| Token subject | the `root` account |
| Default state | off; `services.mcp.enabled` turns on when `web-app-hermes`, `web-app-openclaw` or `web-app-openwebui` is in the deployment |
| Default state | off; `mcp.enabled` turns on when `web-app-hermes`, `web-app-openclaw` or `web-app-openwebui` is in the deployment |
With the service enabled, `tasks/03_mcp.yml` reads the token stored for `administrator` under this role's id, probes it against the running instance with a JSON-RPC `initialize` call, mints a replacement through `gitlab-rails runner` when the stored token is missing or rejected, writes the fresh token back through `sys-token-store`, and fails the deploy when the re-probe is still rejected. The minted token is a personal access token carrying the `mcp` scope with a 364-day expiry. That scope is filtered out of the interactive token picker, so tokens for this endpoint are created programmatically.
The role attaches to the shared overlay declared in `meta/networks.yml` so client containers can reach the endpoint container-to-container; the overlay alias resolves to workhorse.
Tool categories exposed at the pinned version cover issues and work items, merge requests (including diffs and conflicts), pipelines and jobs, labels, project and group search, repository files and commits, and instance metadata. The set includes mutating tools (`create_merge_request`, `create_workitem_note`, `link_work_items`). GitLab enforces no server-side read-only mode: the `mcp` scope grants both read and create access, and the only restriction mechanism is the per-request `X-Gitlab-Enabled-Mcp-Server-Tools` header, which the clients in this repository do not send. `services.mcp.tools.read_only_default` and `services.mcp.tools.mutating_tools_enabled` are declarative metadata, not an enforced policy. Every tool call runs with the blast radius of the `root` account.
Tool categories exposed at the pinned version cover issues and work items, merge requests (including diffs and conflicts), pipelines and jobs, labels, project and group search, repository files and commits, and instance metadata. The set includes mutating tools (`create_merge_request`, `create_workitem_note`, `link_work_items`). GitLab enforces no server-side read-only mode: the `mcp` scope grants both read and create access, and the only restriction mechanism is the per-request `X-Gitlab-Enabled-Mcp-Server-Tools` header, which the clients in this repository do not send. `mcp.tools.read_only_default` and `mcp.tools.mutating_tools_enabled` are declarative metadata, not an enforced policy. Every tool call runs with the blast radius of the `root` account.
A Playwright scenario asserts that an unauthenticated request to the endpoint is never answered with a 2xx.
@@ -108,11 +101,11 @@ A Playwright scenario asserts that an unauthenticated request to the endpoint is
### Default state
Off. `services.mcp.enabled` is true only while `web-app-hermes`, `web-app-openclaw` or `web-app-openwebui` is part of the deployment. The endpoint additionally requires a Premium or Ultimate licence, so a Community deployment leaves it unreachable regardless of the flag.
Off. `mcp.enabled` is true only while `web-app-hermes`, `web-app-openclaw` or `web-app-openwebui` is part of the deployment. The endpoint additionally requires a Premium or Ultimate licence, so a Community deployment leaves it unreachable regardless of the flag.
### How to disable
Remove the MCP client roles, or pin `services.mcp.enabled: false` for this role. The token is then neither minted nor stored, and the overlay attachment is dropped.
Remove the MCP client roles, or pin `mcp.enabled: false` for this role. The token is then neither minted nor stored, and the overlay attachment is dropped.
## Quick Setup
+55
View File
@@ -0,0 +1,55 @@
---
enabled: true
shared: true
classification: native_server
direction: server
source_url: https://docs.gitlab.com/user/gitlab_duo/model_context_protocol/mcp_server/
minimum_version: "18.0"
transport: streamable_http
exposure: public
auth: bearer_token
auth_subject: service_account
implementation: native
credential:
owner: mcp-web-app-gitlab
source: token_store
key: web-app-gitlab
allowed_consumers:
- web-app-openwebui
- web-app-hermes
- web-app-openclaw
endpoint:
service_key: gitlab
path: /api/v4/mcp
port_key: http
limits:
request_bytes: 65536
response_bytes: 1048576
timeout_seconds: 15
concurrent_requests: 4
page_size: 100
result_items: 500
stream_seconds: 300
tools:
allowlist:
- create_issue
- create_merge_request
- create_workitem_note
- get_issue
- get_job_log
- get_mcp_server_version
- get_merge_request
- get_merge_request_commits
- get_merge_request_conflicts
- get_merge_request_diffs
- get_merge_request_pipelines
- get_pipeline_jobs
- get_saved_view_work_items
- get_work_item_types
- get_workitem_notes
- link_work_items
- manage_pipeline
- search
- search_labels
read_only_default: true
mutating_tools_enabled: false
-21
View File
@@ -22,26 +22,6 @@ email:
bond: 1
enabled: "{{ 'web-app-mailu' in group_names }}"
shared: "{{ 'web-app-mailu' in group_names }}"
mcp:
bond: 1
enabled: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names or 'web-app-openwebui' in group_names }}"
shared: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names or 'web-app-openwebui' in group_names }}"
direction: server
source_url: https://docs.gitlab.com/user/gitlab_duo/model_context_protocol/mcp_server/
minimum_version: "18.0"
notes: Beta and tier-gated to Premium or Ultimate; operator-gated here.
transport: streamable_http
exposure: public
auth: bearer_token
auth_subject: administrator
implementation: native
endpoint:
service_key: gitlab
path: /api/v4/mcp
port_key: http
tools:
read_only_default: true
mutating_tools_enabled: false
# nocheck: shared
# nocheck: dynamic-flag
# nocheck: playwright-service-flag
@@ -71,7 +51,6 @@ gitlab:
enabled: true # nocheck: dynamic-flag
shared: true
min_storage: 30GB
# Exception: the entity runs no container of its own (webservice/sidekiq/workhorse/gitaly/shell/migrations carry the real budgets); these keys only satisfy the entity resource contract
cpus: "0.1"
mem_reservation: 128m
mem_limit: 256m
+6 -6
View File
@@ -19,9 +19,6 @@
matomo:
enabled: true
shared: true
mcp:
enabled: true
shared: true
sso:
enabled: true
shared: true
@@ -38,6 +35,9 @@
seaweedfs:
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -54,9 +54,6 @@
matomo:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
sso:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
@@ -73,3 +70,6 @@
seaweedfs:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
@@ -16,9 +16,9 @@ MATOMO_BASE_URL={{ lookup('tls', 'web-app-matomo', 'url.base') | dotenv_quote }}
MATOMO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.matomo.enabled') | string | lower }}
MCP_ENDPOINT_PATH={{ lookup('config', application_id, 'services.mcp.endpoint.path') | dotenv_quote }}
MCP_ENDPOINT_PATH={{ lookup('config', application_id, 'mcp.endpoint.path') | dotenv_quote }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'services.mcp.enabled') | string | lower }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'mcp.enabled') | string | lower }}
PERSONA_ADMINISTRATOR_BLOCKED=true
+2 -2
View File
@@ -31,8 +31,8 @@ GITLAB_OIDC_REDIRECT_URI: "{{ (GITLAB_URL | regex_replace('/$', '')) ~ '/users/a
GITLAB_INIT_ROOT_PASSWORD: "{{ lookup('users', 'administrator').password }}"
GITLAB_ROOT_USERNAME: "root"
GITLAB_MCP_ENABLED: "{{ lookup('config', application_id, 'services.mcp.enabled') }}"
GITLAB_MCP_PATH: "{{ lookup('config', application_id, 'services.mcp.endpoint.path') }}"
GITLAB_MCP_ENABLED: "{{ lookup('config', application_id, 'mcp.enabled') }}"
GITLAB_MCP_PATH: "{{ lookup('config', application_id, 'mcp.endpoint.path') }}"
GITLAB_MCP_TOKEN_NAME: "infinito-mcp"
GITLAB_RAILS_BIN: "/srv/gitlab/bin/rails" # nocheck: literal-path CNG images ship no omnibus gitlab-rails wrapper
GITLAB_MCP_TOKEN_LIFETIME_DAYS: 364
+2 -16
View File
@@ -30,7 +30,6 @@ flowchart LR
svc_sso["sso ❌"]
svc_logout["logout ❌"]
svc_litellm["litellm"]
svc_mcp["mcp"]
svc_dashboard["dashboard"]
svc_matomo["matomo"]
svc_css["css"]
@@ -40,30 +39,17 @@ flowchart LR
end
subgraph dependents [Dependents]
dpt_svc_ai_robot["svc-ai-robot 💻"]
dpt_web_app_gitea["web-app-gitea 🐳🐝"]
dpt_web_app_gitlab["web-app-gitlab 🐳🐝"]
dpt_web_app_homeassistant["web-app-homeassistant 🐳🐝"]
dpt_web_app_jenkins["web-app-jenkins 🐳🐝"]
dpt_web_app_mattermost["web-app-mattermost 🐳🐝"]
dpt_web_app_moodle["web-app-moodle 🐳🐝"]
dpt_web_app_nextcloud["web-app-nextcloud 🐳🐝"]
end
dep_svc_ai_litellm -. "0..1" .-> svc_litellm
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_homeassistant -. "0..1" .-> svc_homeassistant
dep_web_app_homeassistant -. "0..1" .-> svc_mcp
dep_web_app_keycloak -- "1:1" --> svc_sso
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_svc_css -. "0..1" .-> svc_css
svc_hermes -. "0..1" .-> dpt_svc_ai_robot
svc_hermes -. "0..1" .-> dpt_web_app_gitea
svc_hermes -. "0..1" .-> dpt_web_app_gitlab
svc_hermes -. "0..1" .-> dpt_web_app_homeassistant
svc_hermes -. "0..1" .-> dpt_web_app_jenkins
svc_hermes -. "0..1" .-> dpt_web_app_mattermost
svc_hermes -. "0..1" .-> dpt_web_app_moodle
svc_hermes -. "0..1" .-> dpt_web_app_nextcloud
```
@@ -151,12 +137,12 @@ the MCP credentials refuses an unauthenticated caller.
### Default state
Off. `services.mcp.enabled` is false unless an MCP server role is part of the
Off. `mcp.enabled` is false unless an MCP server role is part of the
deployment.
### How to disable
Remove the MCP server roles, or pin `services.mcp.enabled: false` for this role.
Remove the MCP server roles, or pin `mcp.enabled: false` for this role.
The config then renders with an empty `mcp_servers` map and the verification step
is skipped.
+20
View File
@@ -0,0 +1,20 @@
---
enabled: true
shared: true
classification: native_client
direction: client
source_url: https://hermes-agent.nousresearch.com/
supported_version: v2026.7.20
minimum_version: 0.2.0
implementation: native
supported_transports:
- streamable_http
- sse
supported_auths:
- bearer_token
- app_password
- upstream_session
- basic_auth
tools:
read_only_default: true
mutating_tools_enabled: false
-18
View File
@@ -54,24 +54,6 @@ litellm:
enabled: "{{ 'svc-ai-litellm' in group_names }}"
shared: "{{ 'svc-ai-litellm' in group_names }}"
# nocheck: shared-provider
mcp:
bond: 1
enabled: "{{ 'web-app-homeassistant' in group_names }}"
shared: "{{ 'web-app-homeassistant' in group_names }}"
direction: client
source_url: https://hermes-agent.nousresearch.com/
minimum_version: 0.2.0
notes: MCP client since v0.2.0, server since v0.6.0.
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: user
implementation: native
tools:
read_only_default: true
mutating_tools_enabled: false
dashboard:
bond: 1
enabled: "{{ 'web-app-dashboard' in group_names }}"
+6 -6
View File
@@ -8,9 +8,6 @@
litellm:
enabled: true
shared: true
mcp:
enabled: true
shared: true
dashboard:
enabled: true
shared: true
@@ -26,6 +23,9 @@
homeassistant:
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -33,9 +33,6 @@
litellm:
enabled: false
shared: false # nocheck: variants-static-override variant pins a literal off-switch
mcp:
enabled: false
shared: false # nocheck: variants-static-override variant pins a literal off-switch
dashboard:
enabled: false
shared: false # nocheck: variants-static-override variant pins a literal off-switch
@@ -51,3 +48,6 @@
homeassistant:
enabled: false
shared: false # nocheck: variants-static-override variant pins a literal off-switch
mcp:
enabled: false
shared: false
@@ -23,6 +23,6 @@ LITELLM_SERVICE_ENABLED={{ lookup('config', application_id, 'services.litellm.en
MATOMO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.matomo.enabled') | string | lower }}
# nocheck: playwright-service-gate
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'services.mcp.enabled') | string | lower }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'mcp.enabled') | string | lower }}
PERSONA_BIBER_BLOCKED=true
+1 -1
View File
@@ -10,4 +10,4 @@ HERMES_LITELLM_ENABLED: "{{ lookup('config', application_id, 'services.litellm.e
HERMES_LITELLM_API_KEY: "{{ lookup('config', application_id, 'credentials.litellm_api_key') }}"
HERMES_CONFIG_PATH_HOST: "{{ [ lookup('container', application_id, 'directories.config'), 'config.yaml' ] | path_join }}"
HERMES_DOCKER_USER_ID: 10000
HERMES_MCP_SERVERS: "{{ MCP_DISCOVERED_SERVERS if (lookup('config', application_id, 'services.mcp.enabled') | bool) else [] }}"
HERMES_MCP_SERVERS: "{{ MCP_DISCOVERED_SERVERS if (lookup('config', application_id, 'mcp.enabled') | bool) else [] }}"
+3 -8
View File
@@ -17,14 +17,11 @@ flowchart LR
subgraph deps [Dependencies]
dep_svc_bkp_volume_2_local["svc-bkp-volume-2-local 💻"]
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
dep_web_app_hermes["web-app-hermes 🐳🐝"]
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_openclaw["web-app-openclaw 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
end
subgraph role [web-app-homeassistant 🐳🐝]
svc_homeassistant["homeassistant"]
svc_mcp["mcp"]
svc_sso["sso ❌"]
svc_logout["logout ❌"]
svc_dashboard["dashboard"]
@@ -39,9 +36,7 @@ flowchart LR
end
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_hermes -. "0..1" .-> svc_mcp
dep_web_app_keycloak -- "1:1" --> svc_sso
dep_web_app_openclaw -. "0..1" .-> svc_mcp
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
svc_homeassistant -. "0..1" .-> dpt_web_app_flowise
svc_homeassistant -. "0..1" .-> dpt_web_app_hermes
@@ -103,7 +98,7 @@ docker run --rm -it \
## MCP Server
Home Assistant ships the MCP server as a native integration. The role enables it
through `services.mcp.enabled`, which stays true only while an MCP client role is
through `mcp.enabled`, which stays true only while an MCP client role is
part of the deployment.
### Endpoint
@@ -136,7 +131,7 @@ for the current state.
### Default state
Off. `services.mcp.enabled` is false unless `web-app-hermes` or
Off. `mcp.enabled` is false unless `web-app-hermes` or
`web-app-openclaw` is deployed alongside.
### Tool scope
@@ -148,7 +143,7 @@ or actuated through MCP even though the write tools are listed.
### How to disable
Remove the MCP client roles from the deployment, or pin
`services.mcp.enabled: false` for this role in the inventory. The integration is
`mcp.enabled: false` for this role in the inventory. The integration is
then not configured and the endpoint stops answering.
## Credits
+38
View File
@@ -0,0 +1,38 @@
---
enabled: true
shared: true
classification: native_server
direction: server
source_url: https://www.home-assistant.io/integrations/mcp_server/
minimum_version: "2025.2"
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: service_account
implementation: native
credential:
owner: mcp-web-app-homeassistant
source: token_store
key: web-app-homeassistant
allowed_consumers:
- web-app-openwebui
- web-app-hermes
- web-app-openclaw
endpoint:
service_key: homeassistant
path: /api/mcp
port_key: http
tools:
allowlist:
- GetDateTime
- GetLiveContext
- HassBroadcast
- HassCancelAllTimers
- HassListAddItem
- HassListCompleteItem
- HassListRemoveItem
- HassTurnOff
- HassTurnOn
- todo_get_items
read_only_default: true
mutating_tools_enabled: false
@@ -30,28 +30,6 @@ homeassistant:
proxy:
service_key: homeassistant
# nocheck: playwright-service-flag
mcp:
bond: 1
enabled: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names }}"
shared: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names }}"
direction: server
source_url: https://www.home-assistant.io/integrations/mcp_server/
minimum_version: "2025.2"
notes: Native mcp_server integration, Streamable HTTP under /api/mcp.
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: administrator
implementation: native
endpoint:
service_key: homeassistant
path: /api/mcp
port_key: http
tools:
read_only_default: true
mutating_tools_enabled: false
# nocheck: sso, dynamic-flag, playwright-service-flag
sso:
enabled: false
@@ -5,9 +5,6 @@
- services:
container_backup:
enabled: true
mcp:
enabled: true
shared: true
dashboard:
enabled: true
shared: true
@@ -15,16 +12,19 @@
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
dashboard:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
prometheus:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
@@ -3,7 +3,7 @@ CANONICAL_DOMAIN={{ lookup('domain', application_id) | dotenv_quote }}
HOMEASSISTANT_BASE_URL={{ lookup('tls', application_id, 'url.base') | dotenv_quote }}
# nocheck: playwright-service-gate
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'services.mcp.enabled') | string | lower }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'mcp.enabled') | string | lower }}
PERSONA_ADMINISTRATOR_BLOCKED=true
+1 -1
View File
@@ -6,7 +6,7 @@ http_port: "{{ lookup('config', application_id, 'services.homeassistant.ports.lo
HOMEASSISTANT_CONTAINER: "{{ lookup('config', application_id, 'services.homeassistant.name') }}"
HOMEASSISTANT_CONFIG_PATH_HOST: "{{ [ lookup('container', application_id, 'directories.config'), 'configuration.yaml' ] | path_join }}"
HOMEASSISTANT_PORT_INTERNAL: "{{ lookup('config', application_id, 'services.homeassistant.ports.internal.http') }}"
HOMEASSISTANT_MCP_ENABLED: "{{ lookup('config', application_id, 'services.mcp.enabled') }}"
HOMEASSISTANT_MCP_ENABLED: "{{ lookup('config', application_id, 'mcp.enabled') }}"
HOMEASSISTANT_ADMIN_USERNAME: "{{ lookup('users', 'administrator').username }}"
HOMEASSISTANT_ADMIN_PASSWORD: "{{ lookup('config', application_id, 'credentials.administrator_password') }}"
HOMEASSISTANT_TOKEN_CLIENT_NAME: "infinito-mcp"
+1
View File
@@ -34,6 +34,7 @@ flowchart LR
svc_prometheus["prometheus"]
svc_jellyfin["jellyfin"]
svc_container_backup["container_backup"]
svc_jellyfinmcp["jellyfinmcp"]
end
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_svc_db_openldap -. "0..1" .-> svc_ldap
+48
View File
@@ -0,0 +1,48 @@
---
enabled: false
shared: false
classification: adapter_server
direction: server
source_url: https://s.infinito.nexus/code
supported_version: 1.0.0
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: service_account
implementation: adapter
credential:
owner: mcp-web-app-jellyfin
source: credentials
key: mcp_bearer
allowed_consumers:
- web-app-openwebui
- web-app-hermes
- web-app-openclaw
endpoint:
service_key: jellyfinmcp
path: /mcp
port_key: http
adapter:
type: openapi_allowlist
image: infinito/mcp-adapter
version: 1.0.0
digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
upstream_api_version: v1
specification_path: roles/web-app-jellyfin/files/mcp/tools.json
specification_sha256: sha256:08776fdcbed9f362d76e9954881a9184362da85b24e2e503f8259fd664fd85f5
limits:
request_bytes: 65536
response_bytes: 1048576
timeout_seconds: 15
concurrent_requests: 4
page_size: 100
result_items: 100
stream_seconds: 300
tools:
allowlist:
- jellyfin_get_item
- jellyfin_get_playback_status
- jellyfin_search_library
schema_sha256: sha256:08776fdcbed9f362d76e9954881a9184362da85b24e2e503f8259fd664fd85f5
read_only_default: true
mutating_tools_enabled: false
+15
View File
@@ -65,3 +65,18 @@ container_backup: # nocheck: bond-baseline backup host joins outside the variant
bond: 1
enabled: "{{ 'svc-bkp-volume-2-local' in group_names }}"
shared: true
# nocheck: playwright-service-flag internal adapter sidecar, covered by the role's MCP guest spec
# nocheck: dockerfile-custom the Dockerfile is owned by svc-ai-mcp-adapter and staged per instance, so every adapter-backed provider shares one audited build
jellyfinmcp:
image: python
# nocheck: docker-version base of the self-built stdlib adapter, not the served application
version: "3.13-slim"
custom: true
name: jellyfinmcp
cpus: "0.25"
mem_reservation: 16m
mem_limit: 24m
pids_limit: 64
ports:
internal:
http: 8080
+9
View File
@@ -24,6 +24,9 @@
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -50,6 +53,9 @@
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
- services:
container_backup:
enabled: false
@@ -75,3 +81,6 @@
sso:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
+3 -8
View File
@@ -18,10 +18,8 @@ flowchart LR
dep_svc_bkp_volume_2_local["svc-bkp-volume-2-local 💻"]
dep_svc_db_openldap["svc-db-openldap 🐳🐝"]
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
dep_web_app_hermes["web-app-hermes 🐳🐝"]
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_openclaw["web-app-openclaw 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_svc_css["web-svc-css 💻"]
dep_web_svc_logout["web-svc-logout 🐳🐝"]
@@ -34,17 +32,14 @@ flowchart LR
svc_sso["sso"]
svc_ldap["ldap"]
svc_jenkins["jenkins"]
svc_mcp["mcp"]
svc_css["css"]
svc_container_backup["container_backup"]
end
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_svc_db_openldap -. "0..1" .-> svc_ldap
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_hermes -. "0..1" .-> svc_mcp
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_openclaw -. "0..1" .-> svc_mcp
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_svc_css -. "0..1" .-> svc_css
dep_web_svc_logout -. "0..1" .-> svc_logout
@@ -76,7 +71,7 @@ The role exposes Jenkins as an MCP server through the [MCP Server plugin](https:
| Subject | administrator |
| Tools | read-only by default, mutating tools off |
Enable the surface by deploying `web-app-hermes` or `web-app-openclaw` alongside Jenkins, or by forcing `services.mcp.enabled` on. Open WebUI skips the server because it cannot present basic auth.
Enable the surface by deploying `web-app-hermes` or `web-app-openclaw` alongside Jenkins, or by forcing `mcp.enabled` on. Open WebUI skips the server because it cannot present basic auth.
At boot, `files/mcp-api-token.groovy` runs from `init.groovy.d` and mints an API token named `infinito-mcp` for the administrator account, writing the plain value to `/var/jenkins_home/secrets/infinito-mcp.token`. `tasks/01_mcp.yml` then reads that value out of the container, persists it through `sys-token-store`, and hard-fails the deploy when the controller rejects it. `MCP_DISCOVERED_SERVERS` picks the token up from the store and the client roles build the header via the `mcp_authorization` filter.
@@ -92,7 +87,7 @@ curl -u "<administrator>:<apiToken>" \
### Default state
Off. `services.mcp.enabled` is true only while `web-app-hermes` or
Off. `mcp.enabled` is true only while `web-app-hermes` or
`web-app-openclaw` is part of the deployment.
### Authorization subject
@@ -110,7 +105,7 @@ unauthenticated `tools/list` is refused and returns no tool inventory.
### How to disable
Remove the MCP client roles, or pin `services.mcp.enabled: false` for this role.
Remove the MCP client roles, or pin `mcp.enabled: false` for this role.
The MCP Server plugin is then not installed and the boot hook mints no API token.
## Quick Setup
+46
View File
@@ -0,0 +1,46 @@
---
enabled: true
shared: true
classification: plugin_server
direction: server
source_url: https://plugins.jenkins.io/mcp-server/
minimum_version: "2.462"
transport: streamable_http
exposure: internal
auth: basic_auth
auth_subject: service_account
implementation: plugin
credential:
owner: mcp-web-app-jenkins
source: token_store
key: web-app-jenkins
allowed_consumers:
- web-app-hermes
- web-app-openclaw
endpoint:
service_key: jenkins
path: /mcp-server/mcp
port_key: http
tools:
allowlist:
- findJobsWithScmUrl
- getBuild
- getBuildChangeSets
- getBuildLog
- getBuildScm
- getFlakyFailures
- getJob
- getJobScm
- getJobs
- getQueueItem
- getReplayScripts
- getStatus
- getTestResults
- rebuildBuild
- replayBuild
- searchBuildLog
- triggerBuild
- updateBuild
- whoAmI
read_only_default: true
mutating_tools_enabled: false
-20
View File
@@ -54,26 +54,6 @@ jenkins:
mem_reservation: 1g
mem_limit: 2g
pids_limit: 1024
mcp:
bond: 1
enabled: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names }}"
shared: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names }}"
direction: server
source_url: https://plugins.jenkins.io/mcp-server/
minimum_version: "2.462"
notes: Plugin based; pinned like every other Jenkins plugin.
transport: streamable_http
exposure: internal
auth: basic_auth
auth_subject: administrator
implementation: plugin
endpoint:
service_key: jenkins
path: /mcp-server/mcp
port_key: http
tools:
read_only_default: true
mutating_tools_enabled: false
# nocheck: playwright-service-flag — css consumer, no Playwright surface (provider: web-svc-css)
css:
bond: 1
+9 -9
View File
@@ -30,9 +30,9 @@
css:
enabled: true
shared: true
mcp:
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -58,9 +58,9 @@
css:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
mcp:
enabled: false
shared: false
- services:
container_backup:
enabled: false
@@ -86,6 +86,6 @@
prometheus:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
mcp:
enabled: false
shared: false
@@ -20,7 +20,7 @@ MATOMO_BASE_URL={{ lookup('tls', 'web-app-matomo', 'url.base') | dotenv_quote }}
MATOMO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.matomo.enabled') | string | lower }}
MCP_ENDPOINT_PATH={{ lookup('config', application_id, 'services.mcp.endpoint.path') | dotenv_quote }}
MCP_ENDPOINT_PATH={{ lookup('config', application_id, 'mcp.endpoint.path') | dotenv_quote }}
MCP_SERVICE_ENABLED={{ JENKINS_MCP_ENABLED | string | lower }}
+3 -3
View File
@@ -14,13 +14,13 @@ JENKINS_LDAP_ENABLED: "{{ lookup('config', application_id, 'services.ldap.enable
JENKINS_LOOPBACK_URL: "http://localhost:{{ lookup('config', application_id, 'services.jenkins.ports.internal.http') }}"
JENKINS_MCP_ENABLED: "{{ lookup('config', application_id, 'services.mcp.enabled') }}"
JENKINS_MCP_USERNAME: "{{ lookup('users', 'administrator').username }}"
JENKINS_MCP_ENABLED: "{{ lookup('config', application_id, 'mcp.enabled') }}"
JENKINS_MCP_USERNAME: "{{ lookup('config', application_id, 'mcp.credential.owner') }}"
JENKINS_MCP_TOKEN_NAME: "infinito-mcp"
JENKINS_MCP_TOKEN_FILE: "/var/jenkins_home/secrets/infinito-mcp.token"
JENKINS_MCP_IDENTITY_URL: "{{ JENKINS_LOOPBACK_URL }}/whoAmI/api/json"
JENKINS_MCP_READY_URL: "{{ JENKINS_LOOPBACK_URL }}/login"
JENKINS_MCP_URL: "{{ JENKINS_LOOPBACK_URL }}{{ lookup('config', application_id, 'services.mcp.endpoint.path') }}"
JENKINS_MCP_URL: "{{ JENKINS_LOOPBACK_URL }}{{ lookup('config', application_id, 'mcp.endpoint.path') }}"
JENKINS_MCP_INITIALIZE_PAYLOAD:
jsonrpc: "2.0"
id: 1
-1
View File
@@ -36,7 +36,6 @@ flowchart LR
svc_css["css"]
svc_prometheus["prometheus"]
svc_container_backup["container_backup"]
svc_mcp["mcp ❌"]
end
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_svc_db_postgres -. "0..1" .-> svc_postgres
-8
View File
@@ -68,11 +68,3 @@ container_backup: # nocheck: bond-baseline backup host joins outside the variant
bond: 1
enabled: "{{ 'svc-bkp-volume-2-local' in group_names }}"
shared: true
# nocheck: dynamic-flag a blocker block can never be enabled, so a group_names expression would be dead code
mcp:
enabled: false
shared: false
blocker: hosted_only
source_url: https://support.atlassian.com/atlassian-rovo-mcp-server/docs/getting-started-with-the-atlassian-remote-mcp-server/
notes: Rovo MCP is Atlassian Cloud only; this role deploys an unsupported self-hosted container.
+1
View File
@@ -43,6 +43,7 @@ flowchart LR
svc_css["css"]
svc_hcaptcha["hcaptcha"]
svc_prometheus["prometheus"]
svc_listmonkmcp["listmonkmcp"]
end
dep_svc_db_postgres -. "0..1" .-> svc_postgres
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
+48
View File
@@ -0,0 +1,48 @@
---
enabled: false
shared: false
classification: adapter_server
direction: server
source_url: https://s.infinito.nexus/code
supported_version: 1.0.0
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: service_account
implementation: adapter
credential:
owner: mcp-web-app-listmonk
source: credentials
key: mcp_bearer
allowed_consumers:
- web-app-openwebui
- web-app-hermes
- web-app-openclaw
endpoint:
service_key: listmonkmcp
path: /mcp
port_key: http
adapter:
type: openapi_allowlist
image: infinito/mcp-adapter
version: 1.0.0
digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
upstream_api_version: v1
specification_path: roles/web-app-listmonk/files/mcp/tools.json
specification_sha256: sha256:3459dd13cfef868ed4de0123695292c8d75c8d3c612bef78fa8d5eaba297f06e
limits:
request_bytes: 65536
response_bytes: 1048576
timeout_seconds: 15
concurrent_requests: 4
page_size: 100
result_items: 100
stream_seconds: 300
tools:
allowlist:
- listmonk_get_campaign
- listmonk_get_stats
- listmonk_list_campaigns
schema_sha256: sha256:3459dd13cfef868ed4de0123695292c8d75c8d3c612bef78fa8d5eaba297f06e
read_only_default: true
mutating_tools_enabled: false
+15
View File
@@ -82,3 +82,18 @@ prometheus:
bond: 1
enabled: "{{ 'web-app-prometheus' in group_names }}"
shared: "{{ 'web-app-prometheus' in group_names }}"
# nocheck: playwright-service-flag internal adapter sidecar, covered by the role's MCP guest spec
# nocheck: dockerfile-custom the Dockerfile is owned by svc-ai-mcp-adapter and staged per instance, so every adapter-backed provider shares one audited build
listmonkmcp:
image: python
# nocheck: docker-version base of the self-built stdlib adapter, not the served application
version: "3.13-slim"
custom: true
name: listmonkmcp
cpus: "0.25"
mem_reservation: 16m
mem_limit: 24m
pids_limit: 64
ports:
internal:
http: 8080
+6
View File
@@ -34,6 +34,9 @@
css:
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
asset:
enabled: false
@@ -64,3 +67,6 @@
css:
enabled: false
shared: false
mcp:
enabled: false
shared: false
+3 -8
View File
@@ -20,11 +20,9 @@ flowchart LR
dep_svc_db_postgres["svc-db-postgres 🐳🐝"]
dep_svc_db_redis["svc-db-redis 🐳🐝"]
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
dep_web_app_hermes["web-app-hermes 🐳🐝"]
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_mailu["web-app-mailu 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_openclaw["web-app-openclaw 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_app_seaweedfs["web-app-seaweedfs 🐳🐝"]
dep_web_svc_css["web-svc-css 💻"]
@@ -39,7 +37,6 @@ flowchart LR
svc_email["email"]
svc_postgres["postgres"]
svc_mattermost["mattermost"]
svc_mcp["mcp"]
svc_redis["redis"]
svc_minio["minio ❌"]
svc_seaweedfs["seaweedfs"]
@@ -56,11 +53,9 @@ flowchart LR
dep_svc_db_postgres -. "0..1" .-> svc_postgres
dep_svc_db_redis -. "0..1" .-> svc_redis
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_hermes -. "0..1" .-> svc_mcp
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_mailu -. "0..1" .-> svc_email
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_openclaw -. "0..1" .-> svc_mcp
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_app_seaweedfs -. "0..1" .-> svc_seaweedfs
dep_web_svc_css -. "0..1" .-> svc_css
@@ -142,7 +137,7 @@ Mattermost exposes a Model Context Protocol server through the prepackaged Agent
### Default state
`services.mcp.enabled` resolves to `true` only when `web-app-hermes` or `web-app-openclaw` is part of the same deployment, and is `false` otherwise. While it is `true` the deploy:
`mcp.enabled` resolves to `true` only when `web-app-hermes` or `web-app-openclaw` is part of the same deployment, and is `false` otherwise. While it is `true` the deploy:
- sets `MM_SERVICESETTINGS_ENABLEUSERACCESSTOKENS=true`,
- enables the `mattermost-ai` plugin through `mmctl --local`,
@@ -170,14 +165,14 @@ The endpoint serves the Agents plugin's native Mattermost tool catalog:
The catalog includes mutating entries (`create`, `update`, `archive`, `delete`,
`send`, `upload`). The Agents plugin exposes no filter, scope or permission flag
that removes them, so `services.mcp.tools.mutating_tools_enabled: false` records
that removes them, so `mcp.tools.mutating_tools_enabled: false` records
the deployment's intent rather than an enforced state. Every call is bounded by
the permissions of the account the bearer token belongs to, which here is the
administrator.
### How to disable
Remove the MCP client roles, or pin `services.mcp.enabled: false` for this role. The Agents plugin's MCP server is then left switched off and no personal access token is issued.
Remove the MCP client roles, or pin `mcp.enabled: false` for this role. The Agents plugin's MCP server is then left switched off and no personal access token is issued.
## Configuration
+44
View File
@@ -0,0 +1,44 @@
---
enabled: true
shared: true
classification: plugin_server
direction: server
source_url: https://docs.mattermost.com/agents/mcpserver/README.html
minimum_version: "10.0"
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: service_account
implementation: plugin
credential:
owner: mcp-web-app-mattermost
source: token_store
key: web-app-mattermost
allowed_consumers:
- web-app-openwebui
- web-app-hermes
- web-app-openclaw
endpoint:
service_key: mattermost
path: /plugins/mattermost-ai/mcp-server/mcp
port_key: http
tools:
allowlist:
- add_user_to_channel
- create_channel
- create_post
- dm
- get_channel_info
- get_channel_members
- get_team_info
- get_team_members
- get_user_channels
- group_message
- list_agents
- read_channel
- read_file
- read_post
- search_posts
- search_users
read_only_default: true
mutating_tools_enabled: false
@@ -58,26 +58,6 @@ mattermost:
- web-app-keycloak
- web-app-mailu
lifecycle: beta
mcp:
bond: 1
enabled: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names }}"
shared: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names }}"
direction: server
source_url: https://docs.mattermost.com/agents/mcpserver/README.html
minimum_version: "10.0"
notes: Ships with the Agents plugin.
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: administrator
implementation: plugin
endpoint:
service_key: mattermost
path: /plugins/mattermost-ai/mcp-server/mcp
port_key: http
tools:
read_only_default: true
mutating_tools_enabled: false
# nocheck: dynamic-flag
# nocheck: playwright-service-flag — DB engine, covered by role-local integration tests
redis:
+6 -6
View File
@@ -19,9 +19,6 @@
matomo:
enabled: true
shared: true
mcp:
enabled: true
shared: true
sso:
enabled: true
shared: true
@@ -38,6 +35,9 @@
css:
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -54,9 +54,6 @@
matomo:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
sso:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
@@ -73,3 +70,6 @@
css:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
@@ -97,5 +97,5 @@
- (mattermost_mcp_toggle_final.rows[0][0] | trim) == 'true'
fail_msg: >-
The Agents plugin still reports mcp.enablePluginServer=false, so
{{ lookup('config', application_id, 'services.mcp.endpoint.path') }}
{{ lookup('config', application_id, 'mcp.endpoint.path') }}
answers 404 and every MCP client would be pointed at a dead endpoint.
@@ -24,7 +24,7 @@ MATOMO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.matomo.enab
MATTERMOST_BASE_URL={{ lookup('tls', 'web-app-mattermost', 'url.base') | dotenv_quote }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'services.mcp.enabled') | string | lower }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'mcp.enabled') | string | lower }}
OIDC_ISSUER_URL={{ OIDC.CLIENT.ISSUER_URL | dotenv_quote }}
+1 -2
View File
@@ -7,10 +7,9 @@ container_port: "{{ lookup('config', application_id, 'services.' ~ entity_name ~
MATTERMOST_CONTAINER: "{{ lookup('config', application_id, 'services.mattermost.name') }}"
MATTERMOST_CONTAINER_ADDRESS: "{{ lookup('container_address', application_id, entity_name) }}"
# Exception: OIDC piggybacks on MM_GITLABSETTINGS_* -- Team Edition has no native OIDC provider for Keycloak
MATTERMOST_OIDC_ENABLED: "{{ lookup('config', application_id, 'services.sso.enabled') }}"
MATTERMOST_MCP_ENABLED: "{{ lookup('config', application_id, 'services.mcp.enabled') }}"
MATTERMOST_MCP_ENABLED: "{{ lookup('config', application_id, 'mcp.enabled') }}"
MATTERMOST_MCP_PLUGIN_ID: mattermost-ai
MATTERMOST_MCP_TOKEN_DESCRIPTION: mcp
+4 -11
View File
@@ -19,12 +19,9 @@ flowchart LR
dep_svc_db_mariadb["svc-db-mariadb 🐳🐝"]
dep_svc_db_openldap["svc-db-openldap 🐳🐝"]
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
dep_web_app_hermes["web-app-hermes 🐳🐝"]
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_mailu["web-app-mailu 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_openclaw["web-app-openclaw 🐳🐝"]
dep_web_app_openwebui["web-app-openwebui 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_svc_css["web-svc-css 💻"]
dep_web_svc_logout["web-svc-logout 🐳🐝"]
@@ -36,7 +33,6 @@ flowchart LR
svc_matomo["matomo"]
svc_email["email"]
svc_ldap["ldap"]
svc_mcp["mcp"]
svc_mariadb["mariadb"]
svc_moodle["moodle"]
svc_nginx["nginx"]
@@ -49,12 +45,9 @@ flowchart LR
dep_svc_db_mariadb -. "0..1" .-> svc_mariadb
dep_svc_db_openldap -. "0..1" .-> svc_ldap
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_hermes -. "0..1" .-> svc_mcp
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_mailu -. "0..1" .-> svc_email
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_openclaw -. "0..1" .-> svc_mcp
dep_web_app_openwebui -. "0..1" .-> svc_mcp
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_svc_css -. "0..1" .-> svc_css
dep_web_svc_logout -. "0..1" .-> svc_logout
@@ -120,7 +113,7 @@ The role exposes a Model Context Protocol surface through the `webservice_mcp` p
| Transport | Streamable HTTP (JSON-RPC 2.0 over `POST`) |
| Auth | `Authorization: Bearer <token>`, a Moodle web-service token |
| Subject | the `administrator` account |
| Default state | off; `services.mcp.enabled` turns true when `web-app-hermes`, `web-app-openclaw` or `web-app-openwebui` is in the inventory |
| Default state | off; `mcp.enabled` turns true when `web-app-hermes`, `web-app-openclaw` or `web-app-openwebui` is in the inventory |
Provisioning runs in `tasks/06_mcp.yml`: it copies the plugin into the code volume, runs `admin/cli/upgrade.php`, enables `enablewebservices`, appends `mcp` to `webserviceprotocols`, creates the `infinito_mcp` external service, attaches the read-only functions, mints the permanent token and stores it under `users.administrator.tokens['web-app-moodle']`.
@@ -131,11 +124,11 @@ Tool categories exposed by the external service:
- user lookup (`core_user_get_users_by_field`)
- calendar events (`core_calendar_get_calendar_events`)
All of them are read-only; `services.mcp.tools.mutating_tools_enabled` is `false` and no write function is attached.
All of them are read-only; `mcp.tools.mutating_tools_enabled` is `false` and no write function is attached.
### Default state
Off. `services.mcp.enabled` is true only while `web-app-hermes`, `web-app-openclaw` or `web-app-openwebui` is part of the deployment.
Off. `mcp.enabled` is true only while `web-app-hermes`, `web-app-openclaw` or `web-app-openwebui` is part of the deployment.
### Authorization subject
@@ -151,7 +144,7 @@ Moodle compares every request against `$CFG->wwwroot`. A probe that reaches the
### How to disable
Remove the MCP client roles, or pin `services.mcp.enabled: false` for this role. The web-service token is then not issued and the protocol plugin stays unconfigured.
Remove the MCP client roles, or pin `mcp.enabled: false` for this role. The web-service token is then not issued and the protocol plugin stays unconfigured.
## Image source
+41
View File
@@ -0,0 +1,41 @@
---
enabled: true
shared: true
classification: plugin_server
direction: server
source_url: https://moodle.org/plugins/webservice_mcp
minimum_version: "4.3"
transport: streamable_http
exposure: public
auth: bearer_token
auth_subject: service_account
implementation: plugin
credential:
owner: mcp-web-app-moodle
source: token_store
key: web-app-moodle
allowed_consumers:
- web-app-openwebui
- web-app-hermes
- web-app-openclaw
endpoint:
service_key: nginx
path: /webservice/mcp/server.php
port_key: http
limits:
request_bytes: 65536
response_bytes: 1048576
timeout_seconds: 15
concurrent_requests: 4
page_size: 100
result_items: 500
stream_seconds: 300
tools:
allowlist:
- core_calendar_get_calendar_events
- core_course_get_courses
- core_enrol_get_users_courses
- core_user_get_users_by_field
- core_webservice_get_site_info
read_only_default: true
mutating_tools_enabled: false
-20
View File
@@ -25,26 +25,6 @@ ldap:
bond: 1
enabled: "{{ 'svc-db-openldap' in group_names }}"
shared: "{{ 'svc-db-openldap' in group_names }}"
mcp:
bond: 1
enabled: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names or 'web-app-openwebui' in group_names }}"
shared: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names or 'web-app-openwebui' in group_names }}"
direction: server
source_url: https://moodle.org/plugins/webservice_mcp
minimum_version: "4.3"
notes: Plugin based; must match the pinned Moodle release.
transport: streamable_http
exposure: public
auth: bearer_token
auth_subject: administrator
implementation: plugin
endpoint:
service_key: nginx
path: /webservice/mcp/server.php
port_key: http
tools:
read_only_default: true
mutating_tools_enabled: false
# nocheck: playwright-service-flag — DB engine, covered by role-local integration tests
mariadb:
bond: 1
+9 -9
View File
@@ -30,9 +30,6 @@
matomo:
enabled: true
shared: true
mcp:
enabled: true
shared: true
sso:
enabled: true
shared: true
@@ -40,6 +37,9 @@
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -68,12 +68,12 @@
matomo:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
prometheus:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
- services:
container_backup:
enabled: false
@@ -98,12 +98,12 @@
matomo:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
sso:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
prometheus:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
@@ -25,5 +25,5 @@ MATOMO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.matomo.enab
EMAIL_SERVICE_ENABLED={{ lookup('config', application_id, 'services.email.enabled') | string | lower }}
LDAP_SERVICE_ENABLED={{ lookup('config', application_id, 'services.ldap.enabled') | string | lower }}
LAM_SERVICE_ENABLED={{ lookup('config', application_id, 'services.lam.enabled', false) | string | lower }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'services.mcp.enabled') | string | lower }}
MCP_ENDPOINT_PATH={{ lookup('config', application_id, 'services.mcp.endpoint.path') | dotenv_quote }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'mcp.enabled') | string | lower }}
MCP_ENDPOINT_PATH={{ lookup('config', application_id, 'mcp.endpoint.path') | dotenv_quote }}
+3 -3
View File
@@ -51,15 +51,15 @@ MOODLE_CRON_CONTAINER: "{{ lookup('config', application_id, 'services.cron.name'
MOODLE_OIDC_ENABLED: "{{ lookup('config', application_id, 'services.sso.enabled') }}"
MOODLE_LDAP_ENABLED: "{{ lookup('config', application_id, 'services.ldap.enabled') }}"
MOODLE_MCP_ENABLED: "{{ lookup('config', application_id, 'services.mcp.enabled') }}"
MOODLE_MCP_ENABLED: "{{ lookup('config', application_id, 'mcp.enabled') }}"
MOODLE_MCP_PLUGIN_VERSION: "v0.6.0"
MOODLE_MCP_PLUGIN_ARCHIVE_URL: "https://github.com/onbirdev/moodle-webservice_mcp/archive/refs/tags/{{ MOODLE_MCP_PLUGIN_VERSION }}.zip"
MOODLE_MCP_PLUGIN_RELPATH: "{{ [MOODLE_WEBSERVICE_SUBDIR, 'mcp'] | path_join }}"
MOODLE_MCP_PLUGIN_SOURCE_DIR: "{{ [MOODLE_SOURCE_DIR, MOODLE_MCP_PLUGIN_RELPATH] | path_join }}"
MOODLE_MCP_PLUGIN_DIR: "{{ [MOODLE_CODE_DIR, MOODLE_MCP_PLUGIN_RELPATH] | path_join }}"
MOODLE_MCP_SERVICE_KEY: "{{ lookup('config', application_id, 'services.mcp.endpoint.service_key') }}"
MOODLE_MCP_SERVICE_KEY: "{{ lookup('config', application_id, 'mcp.endpoint.service_key') }}"
MOODLE_MCP_PORT: "{{ lookup('config', application_id, 'services.nginx.ports.internal.http') }}"
MOODLE_MCP_URL: "http://{{ MOODLE_MCP_SERVICE_KEY }}:{{ MOODLE_MCP_PORT }}{{ lookup('config', application_id, 'services.mcp.endpoint.path') }}"
MOODLE_MCP_URL: "http://{{ MOODLE_MCP_SERVICE_KEY }}:{{ MOODLE_MCP_PORT }}{{ lookup('config', application_id, 'mcp.endpoint.path') }}"
MOODLE_MCP_EXTERNAL_SERVICE_NAME: "Model Context Protocol"
MOODLE_MCP_EXTERNAL_SERVICE_SHORTNAME: "infinito_mcp"
MOODLE_MCP_EXTERNAL_FUNCTIONS:
+34
View File
@@ -0,0 +1,34 @@
---
enabled: false
shared: false
classification: native_both
direction: both
source_url: https://github.com/n8n-io/n8n/tree/n8n%401.95.3/packages/%40n8n/nodes-langchain/nodes/mcp
supported_version: 1.95.3
minimum_version: 1.88.0
transport: sse
exposure: internal
auth: bearer_token
auth_subject: service_account
implementation: native
credential:
owner: mcp-web-app-n8n
source: token_store
key: web-app-n8n
allowed_consumers:
- web-app-hermes
- web-app-openclaw
- web-app-openwebui
supported_transports:
- sse
supported_auths:
- bearer_token
- app_password
- upstream_session
endpoint:
service_key: n8n
path: /webhook/infinito-mcp/sse
port_key: http
tools:
read_only_default: true
mutating_tools_enabled: false
+6
View File
@@ -26,6 +26,9 @@
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -53,3 +56,6 @@
prometheus:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
+5 -8
View File
@@ -57,7 +57,6 @@ flowchart LR
svc_mariadb["mariadb"]
svc_nextcloud["nextcloud"]
svc_proxy["proxy"]
svc_mcp["mcp"]
svc_context_agent["context_agent"]
svc_cron["cron"]
svc_talk["talk"]
@@ -96,7 +95,6 @@ flowchart LR
dep_web_app_discourse -. "0..1" .-> svc_discourse
dep_web_app_gitlab -. "0..1" .-> svc_gitlab
dep_web_app_hermes -. "0..1" .-> svc_context_agent
dep_web_app_hermes -. "0..1" .-> svc_mcp
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_mailu -. "0..1" .-> svc_email
dep_web_app_mastodon -. "0..1" .-> svc_mastodon
@@ -104,7 +102,6 @@ flowchart LR
dep_web_app_matrix -. "0..1" .-> svc_matrix
dep_web_app_mattermost -. "0..1" .-> svc_mattermost
dep_web_app_openclaw -. "0..1" .-> svc_context_agent
dep_web_app_openclaw -. "0..1" .-> svc_mcp
dep_web_app_openproject -. "0..1" .-> svc_openproject
dep_web_app_openwebui -. "0..1" .-> svc_openwebui
dep_web_app_peertube -. "0..1" .-> svc_peertube
@@ -201,11 +198,11 @@ Nextcloud serves a Model Context Protocol endpoint through the AppAPI proxy of t
| Auth | Nextcloud app password, sent as `Authorization: Bearer <app-password>` |
| Identity | the Nextcloud user the app password belongs to; every tool call runs with that user's permissions |
| Implementation | plugin (the `context_agent` ExApp container, registered through the `manual_install` deploy daemon) |
| Default state | off; `services.mcp.enabled` turns on when `web-app-hermes` or `web-app-openclaw` is deployed |
| Default state | off; `mcp.enabled` turns on when `web-app-hermes` or `web-app-openclaw` is deployed |
### Deployment
The `context_agent` service in [`meta/services.yml`](./meta/services.yml) renders only while `services.mcp.enabled` is true. It runs `ghcr.io/nextcloud/context_agent`, listens on its internal port for AppAPI only, and shares `credentials.context_agent_app_secret` with the ExApp registration as `APP_SECRET`.
The `context_agent` service in [`meta/services.yml`](./meta/services.yml) renders only while `mcp.enabled` is true. It runs `ghcr.io/nextcloud/context_agent`, listens on its internal port for AppAPI only, and shares `credentials.context_agent_app_secret` with the ExApp registration as `APP_SECRET`.
[`tasks/03_mcp.yml`](./tasks/03_mcp.yml) waits for the ExApp heartbeat, registers the deploy daemon and the ExApp (route `^/mcp`, verbs `POST,GET,DELETE`, access level `1`), enables the ExApp, mints an app password via `occ user:auth-tokens:add`, persists it through `sys-token-store` under `users.administrator.tokens['web-app-nextcloud']`, and asserts that an authenticated `initialize` call answers `200`.
@@ -215,7 +212,7 @@ The `context_agent` service in [`meta/services.yml`](./meta/services.yml) render
### Tool categories
The ExApp registers every tool category the Context Agent ships, read-only and mutating alike, and each call runs with the permissions of the app-password owner. The 2.7.0 image advertises 94 tools across 23 categories, which are application names (`calendar`, `files`, `mail`, `talk`, …), not operations: each bundles its read and its write tools. `tool_status` therefore switches whole categories, and no setting removes only the mutating ones, so `services.mcp.tools.mutating_tools_enabled: false` records the deployment's intent rather than an enforced state. Disabling a category to bar its write tools also removes its read tools. List the categories and narrow the exposed set with:
The ExApp registers every tool category the Context Agent ships, read-only and mutating alike, and each call runs with the permissions of the app-password owner. The 2.7.0 image advertises 94 tools across 23 categories, which are application names (`calendar`, `files`, `mail`, `talk`, …), not operations: each bundles its read and its write tools. `tool_status` therefore switches whole categories, and no setting removes only the mutating ones, so `mcp.tools.mutating_tools_enabled: false` records the deployment's intent rather than an enforced state. Disabling a category to bar its write tools also removes its read tools. List the categories and narrow the exposed set with:
```bash
occ app_api:app:config:get context_agent tool_status
@@ -237,11 +234,11 @@ An unauthenticated request to the same path answers `404`; [`files/playwright/te
### Default state
Off. `services.mcp.enabled` is true only while `web-app-hermes` or `web-app-openclaw` is part of the deployment.
Off. `mcp.enabled` is true only while `web-app-hermes` or `web-app-openclaw` is part of the deployment.
### How to disable
Remove the MCP client roles, or pin `services.mcp.enabled: false` for this role. The `context_agent` service is then not rendered, the ExApp is not registered with AppAPI, and no app password is minted.
Remove the MCP client roles, or pin `mcp.enabled: false` for this role. The `context_agent` service is then not rendered, the ExApp is not registered with AppAPI, and no app password is minted.
## Documentation
+35
View File
@@ -0,0 +1,35 @@
---
enabled: true
shared: true
classification: plugin_server
direction: server
source_url: https://docs.nextcloud.com/server/latest/admin_manual/ai/app_context_agent.html
minimum_version: "30"
transport: streamable_http
exposure: public
auth: app_password
auth_subject: service_account
implementation: plugin
credential:
owner: mcp-web-app-nextcloud
source: token_store
key: web-app-nextcloud
allowed_consumers:
- web-app-openwebui
- web-app-hermes
- web-app-openclaw
endpoint:
service_key: proxy
path: /index.php/apps/app_api/proxy/context_agent/mcp/
port_key: http
limits:
request_bytes: 65536
response_bytes: 1048576
timeout_seconds: 15
concurrent_requests: 4
page_size: 100
result_items: 500
stream_seconds: 300
tools:
read_only_default: true
mutating_tools_enabled: false
-20
View File
@@ -115,26 +115,6 @@ proxy:
ports:
internal:
http: 80
mcp:
bond: 1
enabled: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names }}"
shared: "{{ 'web-app-hermes' in group_names or 'web-app-openclaw' in group_names }}"
direction: server
source_url: https://docs.nextcloud.com/server/latest/admin_manual/ai/app_context_agent.html
minimum_version: "30"
notes: Context Agent ExApp behind the AppAPI proxy, app-password auth.
transport: streamable_http
exposure: public
auth: app_password
auth_subject: administrator
implementation: plugin
endpoint:
service_key: proxy
path: /index.php/apps/app_api/proxy/context_agent/mcp/
port_key: http
tools:
read_only_default: true
mutating_tools_enabled: false
# nocheck: playwright-service-flag
context_agent:
bond: 1
+24 -24
View File
@@ -30,9 +30,6 @@
dashboard:
enabled: true
shared: true
mcp:
enabled: true
shared: true
context_agent:
enabled: true
shared: true
@@ -100,6 +97,9 @@
peertube:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -119,9 +119,6 @@
dashboard:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
context_agent:
enabled: false
shared: false
@@ -191,6 +188,9 @@
peertube:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
- services:
container_backup:
enabled: false
@@ -210,9 +210,6 @@
dashboard:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
context_agent:
enabled: false
shared: false
@@ -282,6 +279,9 @@
peertube:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
- services:
container_backup:
enabled: false
@@ -301,9 +301,6 @@
dashboard:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
context_agent:
enabled: false
shared: false
@@ -373,6 +370,9 @@
peertube:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
- services:
container_backup:
enabled: false
@@ -392,9 +392,6 @@
dashboard:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
context_agent:
enabled: false
shared: false
@@ -464,6 +461,9 @@
peertube:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
- services:
container_backup:
enabled: false
@@ -483,9 +483,6 @@
dashboard:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
context_agent:
enabled: false
shared: false
@@ -555,6 +552,9 @@
peertube:
enabled: true
shared: true
mcp:
enabled: false
shared: false
- services:
container_backup:
enabled: false
@@ -574,9 +574,6 @@
dashboard:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
context_agent:
enabled: false
shared: false
@@ -646,6 +643,9 @@
peertube:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
- services:
container_backup:
enabled: false
@@ -665,9 +665,6 @@
dashboard:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
context_agent:
enabled: false
shared: false
@@ -737,3 +734,6 @@
peertube:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
@@ -25,7 +25,7 @@ MATOMO_BASE_URL={{ lookup('tls', 'web-app-matomo', 'url.base') | dotenv_quote }}
MATOMO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.matomo.enabled') | string | lower }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'services.mcp.enabled') | string | lower }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'mcp.enabled') | string | lower }}
MOODLE_BASE_URL={{ (lookup('tls', 'web-app-moodle', 'url.base') if (lookup('config', application_id, 'services.moodle.enabled') | bool) else '') | dotenv_quote }}
@@ -33,7 +33,7 @@ NEXTCLOUD_BASE_URL={{ lookup('tls', 'web-app-nextcloud', 'url.base') | dotenv_qu
NEXTCLOUD_DIRECT_LOGIN_PASSWORD={{ lookup('config', application_id, 'credentials.administrator_password') | dotenv_quote }}
NEXTCLOUD_MCP_ENDPOINT_PATH={{ lookup('config', application_id, 'services.mcp.endpoint.path') | dotenv_quote }}
NEXTCLOUD_MCP_ENDPOINT_PATH={{ lookup('config', application_id, 'mcp.endpoint.path') | dotenv_quote }}
NEXTCLOUD_ONLYOFFICE_EXPECTED_DOCUMENT_SERVER_URL={{ NEXTCLOUD_ONLYOFFICE_URL | dotenv_quote }}
+2 -2
View File
@@ -47,8 +47,8 @@ NEXTCLOUD_PROXY_SERVICE: "proxy"
NEXTCLOUD_PROXY_CONTAINER: "{{ lookup('config', application_id, 'services.' ~ NEXTCLOUD_PROXY_SERVICE ~ '.name') }}"
NEXTCLOUD_PROXY_DNS_NAME: "{{ (entity_name ~ '_' ~ NEXTCLOUD_PROXY_SERVICE) if DEPLOYMENT_MODE == 'swarm' else NEXTCLOUD_PROXY_CONTAINER }}"
NEXTCLOUD_MCP_ENABLED: "{{ lookup('config', application_id, 'services.mcp.enabled') }}"
NEXTCLOUD_MCP_PATH: "{{ lookup('config', application_id, 'services.mcp.endpoint.path') }}"
NEXTCLOUD_MCP_ENABLED: "{{ lookup('config', application_id, 'mcp.enabled') }}"
NEXTCLOUD_MCP_PATH: "{{ lookup('config', application_id, 'mcp.endpoint.path') }}"
NEXTCLOUD_MCP_TOKEN_NAME: "infinito-mcp"
NEXTCLOUD_CONTEXT_AGENT_SERVICE: "context_agent"
-1
View File
@@ -41,7 +41,6 @@ flowchart LR
svc_javascript["javascript"]
svc_prometheus["prometheus"]
svc_container_backup["container_backup"]
svc_mcp["mcp ❌"]
end
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_svc_db_openldap -. "0..1" .-> svc_ldap
-9
View File
@@ -96,12 +96,3 @@ container_backup: # nocheck: bond-baseline backup host joins outside the variant
bond: 1
enabled: "{{ 'svc-bkp-volume-2-local' in group_names }}"
shared: true
# nocheck: dynamic-flag a blocker block can never be enabled, so a group_names expression would be dead code
# nocheck: playwright-service-flag no MCP surface is served, so no spec can exercise one
mcp:
enabled: false
shared: false
blocker: hosted_only
source_url: https://apps.odoo.com/apps
notes: Third-party add-ons only; none has been selected and reviewed.
+2 -16
View File
@@ -30,7 +30,6 @@ flowchart LR
svc_kata["kata"]
svc_sso["sso"]
svc_litellm["litellm"]
svc_mcp["mcp"]
svc_logout["logout"]
svc_dashboard["dashboard"]
svc_matomo["matomo"]
@@ -41,31 +40,18 @@ flowchart LR
end
subgraph dependents [Dependents]
dpt_svc_ai_robot["svc-ai-robot 💻"]
dpt_web_app_gitea["web-app-gitea 🐳🐝"]
dpt_web_app_gitlab["web-app-gitlab 🐳🐝"]
dpt_web_app_homeassistant["web-app-homeassistant 🐳🐝"]
dpt_web_app_jenkins["web-app-jenkins 🐳🐝"]
dpt_web_app_mattermost["web-app-mattermost 🐳🐝"]
dpt_web_app_moodle["web-app-moodle 🐳🐝"]
dpt_web_app_nextcloud["web-app-nextcloud 🐳🐝"]
end
dep_svc_ai_litellm -. "0..1" .-> svc_litellm
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_homeassistant -. "0..1" .-> svc_homeassistant
dep_web_app_homeassistant -. "0..1" .-> svc_mcp
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_svc_css -. "0..1" .-> svc_css
dep_web_svc_logout -. "0..1" .-> svc_logout
svc_openclaw -. "0..1" .-> dpt_svc_ai_robot
svc_openclaw -. "0..1" .-> dpt_web_app_gitea
svc_openclaw -. "0..1" .-> dpt_web_app_gitlab
svc_openclaw -. "0..1" .-> dpt_web_app_homeassistant
svc_openclaw -. "0..1" .-> dpt_web_app_jenkins
svc_openclaw -. "0..1" .-> dpt_web_app_mattermost
svc_openclaw -. "0..1" .-> dpt_web_app_moodle
svc_openclaw -. "0..1" .-> dpt_web_app_nextcloud
```
@@ -148,12 +134,12 @@ gateway holding the MCP credentials refuses an unauthenticated caller.
### Default state
Off. `services.mcp.enabled` is false unless an MCP server role is part of the
Off. `mcp.enabled` is false unless an MCP server role is part of the
deployment.
### How to disable
Remove the MCP server roles, or pin `services.mcp.enabled: false` for this role.
Remove the MCP server roles, or pin `mcp.enabled: false` for this role.
The rendered config then contains no MCP server entry.
## Credits
+20
View File
@@ -0,0 +1,20 @@
---
enabled: true
shared: true
classification: native_client
direction: client
source_url: https://openclaw.ai/
supported_version: "2026.7.1"
minimum_version: "2025.1"
implementation: native
supported_transports:
- streamable_http
- sse
supported_auths:
- bearer_token
- app_password
- upstream_session
- basic_auth
tools:
read_only_default: true
mutating_tools_enabled: false
-18
View File
@@ -55,24 +55,6 @@ litellm:
enabled: "{{ 'svc-ai-litellm' in group_names }}"
shared: "{{ 'svc-ai-litellm' in group_names }}"
# nocheck: shared-provider
mcp:
bond: 1
enabled: "{{ 'web-app-homeassistant' in group_names }}"
shared: "{{ 'web-app-homeassistant' in group_names }}"
direction: client
source_url: https://openclaw.ai/
minimum_version: "2025.1"
notes: Native @modelcontextprotocol/sdk, client and server.
transport: streamable_http
exposure: internal
auth: bearer_token
auth_subject: user
implementation: native
tools:
read_only_default: true
mutating_tools_enabled: false
logout:
bond: 1
enabled: "{{ 'web-svc-logout' in group_names }}"
+6 -6
View File
@@ -11,9 +11,6 @@
litellm:
enabled: true
shared: true
mcp:
enabled: true
shared: true
logout:
enabled: true
shared: true
@@ -32,6 +29,9 @@
homeassistant:
enabled: true
shared: true
mcp:
enabled: true
shared: true
- services:
container_backup:
enabled: false
@@ -42,9 +42,6 @@
litellm:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
logout:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
@@ -63,3 +60,6 @@
homeassistant:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
mcp:
enabled: false
shared: false
@@ -22,7 +22,7 @@ LOGOUT_SERVICE_ENABLED={{ lookup('config', application_id, 'services.logout.enab
MATOMO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.matomo.enabled') | string | lower }}
# nocheck: playwright-service-gate
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'services.mcp.enabled') | string | lower }}
MCP_SERVICE_ENABLED={{ lookup('config', application_id, 'mcp.enabled') | string | lower }}
NODE_TLS_REJECT_UNAUTHORIZED=0
+1 -1
View File
@@ -7,4 +7,4 @@ OPENCLAW_CONTAINER: "{{ lookup('config', application_id, 'services.openclaw.name
OPENCLAW_PORT_INTERNAL: "{{ lookup('config', application_id, 'services.openclaw.ports.internal.http') }}"
OPENCLAW_URL: "{{ lookup('tls', application_id, 'url.base') }}"
OPENCLAW_CONFIG_PATH_HOST: "{{ [ lookup('container', application_id, 'directories.config'), 'openclaw.json' ] | path_join }}"
OPENCLAW_MCP_SERVERS: "{{ MCP_DISCOVERED_SERVERS if (lookup('config', application_id, 'services.mcp.enabled') | bool) else [] }}"
OPENCLAW_MCP_SERVERS: "{{ MCP_DISCOVERED_SERVERS if (lookup('config', application_id, 'mcp.enabled') | bool) else [] }}"
Loaded 100 of 143 files, more files were not shown because too many files have changed in this diff. Show more