Compare commits

...
Author SHA1 Message Date
Cédric VerstraetenandCopilot d4a8479a9b Add configurable Hub API MCP endpoint (0.177.0)
Expose MCP enabled/origin settings in Hub chart values and API deployment, with schema validation, documentation and rendering checks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-08 20:15:29 +01:00
6 changed files with 144 additions and 1 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.176.0
version: 0.177.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
+30
View File
@@ -335,6 +335,34 @@ ownership on existing optional `LOCAL_CACHE_DIRECTORY` mounts. Its liveness
endpoint is `GET /api/health`. Wire those settings in the actual Vault deployment
only when a compatible image release is selected; no migration is required.
### MCP
Deploy a Hub API image supporting MCP, then enable the read-only Streamable HTTP
endpoint at `/mcp`:
```yaml
kerberoshub:
api:
mcp:
enabled: true
allowedOrigins: []
```
After upgrading the Helm release, an unauthenticated request to `/mcp` should
return `401` instead of `404`. Connect using HTTPS and an existing Hub access
token in `Authorization: Bearer <token>`; browser-session JWTs and cookies cannot
authenticate MCP. Recording tools require `media.read`, device tools require
`devices.read`, and both require effective resource grants.
VS Code and other clients without an `Origin` header need no `allowedOrigins`.
For browser MCP clients, list exact origins such as `https://agent.example.com`.
Wildcards and trailing slashes are not supported. These origins are independent
of REST CORS; frontend and REST origins are not automatically copied into MCP.
MCP stays disabled by default, and these settings apply only to Hub API pods
in `all` and `ui` modes. The chart does not change the API image tag.
Validate rendering with `bash scripts/check-hub-mcp.sh` from the repository root.
### Passkeys
Users and sub-users can create and manage their own passkeys in **Profile >
@@ -697,6 +725,8 @@ kerberospipeline:
| `kerberoshub.api.schema` | The protocol to serve the Kerberos Hub API, `'http'` or `'https'`. | `"https"` |
| `kerberoshub.api.url` | The Kerberos Hub API ingress to access the API. | `"api.yourdomain.com"` |
| `kerberoshub.api.corsAllowedOrigins` | Additional complete HTTP(S) origins allowed to call the Hub API. Configured frontend origins remain allowed automatically. | `[]` |
| `kerberoshub.api.mcp.enabled` | Expose the read-only `/mcp` endpoint; sets `MCP_ENABLED`. Requires a compatible Hub API image. | `false` |
| `kerberoshub.api.mcp.allowedOrigins` | Exact HTTP(S) origins for browser MCP clients, joined into `MCP_ALLOWED_ORIGINS`. Independent of REST CORS; no wildcards or trailing slash. | `[]` |
| `kerberoshub.api.disableAgentHeartbeat` | Temporarily stop serving the polled `GET /devices` list (hub-api returns `204` without database reads). Devices still load once through `/session/bootstrap`; online/offline status stops refreshing. | `false` |
| `kerberoshub.api.mediaProxy.enabled` | Serve media through hub-api (`/media/proxy`) instead of handing out Vault/storage URLs, hiding the storage endpoint from clients. | `false` |
| `kerberoshub.api.mediaProxy.publicUrl` | Public hub-api base URL used in proxied media URLs. Defaults to `{schema}://{url}`. | `""` |
@@ -323,6 +323,11 @@ spec:
{{- end }}
- name: CORS_ALLOWED_ORIGINS
value: {{ join "," $corsOrigins | quote }}
{{- $mcp := .Values.kerberoshub.api.mcp | default dict }}
- name: MCP_ENABLED
value: {{ $mcp.enabled | default false | quote }}
- name: MCP_ALLOWED_ORIGINS
value: {{ join "," ($mcp.allowedOrigins | default list) | quote }}
{{ if .Values.isPrivate }}
- name: KERBEROS_PRIVATE_CLOUD
value: "true"
+16
View File
@@ -62,6 +62,22 @@
"api": {
"type": "object",
"properties": {
"mcp": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {"type": "boolean"},
"allowedOrigins": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string",
"pattern": "^https?://[^/?#*@,\\s]+$"
},
"description": "Exact HTTP(S) origins for browser MCP clients, without wildcards, credentials, paths or trailing slashes. Independent of REST CORS."
}
}
},
"passkeys": {
"type": "object",
"additionalProperties": false,
+6
View File
@@ -370,6 +370,12 @@ kerberoshub:
# Additional complete HTTP(S) origins allowed to call the Hub API.
# The configured frontend origins remain allowed automatically.
corsAllowedOrigins: []
# Read-only Streamable HTTP MCP endpoint at /mcp; requires a compatible API image.
mcp:
enabled: false
# Exact HTTP(S) origins for browser MCP clients; no wildcards or trailing slash.
# Clients without an Origin header (such as VS Code) need no entries.
allowedOrigins: []
# Temporarily stop serving the polled GET /devices list: hub-api answers 204
# without querying MongoDB. The frontend still loads devices once through
# /session/bootstrap, but device online/offline status stops refreshing.
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -euo pipefail
CHART_DIR="${1:-charts/hub}"
extract_mcp() {
awk '
/^---/ { deployment=0; name=""; variable="" }
/^kind: Deployment$/ { deployment=1 }
deployment && /^ name:/ { name=$2 }
/- name: MCP_/ { variable=$3; next }
variable != "" && /value:/ {
value=$0
sub(/^[[:space:]]*value: /, "", value)
print (deployment ? name : "non-deployment"), variable, value
variable=""
}
' | sort
}
assert_mcp() {
local rendered="$1" expected="$2" actual
actual="$(printf '%s\n' "$rendered" | extract_mcp)"
if [[ "$actual" != "$expected" ]]; then
printf 'Unexpected MCP environment:\n%s\nExpected:\n%s\n' "$actual" "$expected" >&2
return 1
fi
}
defaults="$(printf '%s\n' 'hub-api MCP_ALLOWED_ORIGINS ""' 'hub-api MCP_ENABLED "false"')"
enabled="$(printf '%s\n' \
'hub-api MCP_ALLOWED_ORIGINS "https://agent.example.com,http://localhost:4200"' \
'hub-api MCP_ENABLED "true"')"
flags=(
--set kerberoshub.api.mcp.enabled=true
--set-json 'kerberoshub.api.mcp.allowedOrigins=["https://agent.example.com","http://localhost:4200"]'
)
for mode in all ui pipeline; do
default_render="$(helm template hub "$CHART_DIR" --set "mode=$mode")"
configured_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${flags[@]}")"
helm lint "$CHART_DIR" --quiet --set "mode=$mode" "${flags[@]}"
if [[ "$mode" == pipeline ]]; then
assert_mcp "$default_render" ""
assert_mcp "$configured_render" ""
[[ "$default_render" == "$configured_render" ]]
else
assert_mcp "$default_render" "$defaults"
assert_mcp "$configured_render" "$enabled"
rest_default="$(grep -A1 -- '- name: CORS_ALLOWED_ORIGINS' <<< "$default_render")"
rest_configured="$(grep -A1 -- '- name: CORS_ALLOWED_ORIGINS' <<< "$configured_render")"
[[ "$rest_default" == "$rest_configured" ]]
fi
echo "OK ($mode): MCP defaults, overrides, API-only environment and REST CORS isolation"
done
assert_mcp "$(helm template hub "$CHART_DIR" --set kerberoshub.api.mcp.enabled=true)" \
"$(printf '%s\n' 'hub-api MCP_ALLOWED_ORIGINS ""' 'hub-api MCP_ENABLED "true"')"
assert_mcp "$(helm template hub "$CHART_DIR" --set kerberoshub.api.mcp=null)" "$defaults"
assert_mcp "$(helm template hub "$CHART_DIR" "${flags[@]}" --set kerberoshub.api.mcp.enabled=false)" \
"$(printf '%s\n' 'hub-api MCP_ALLOWED_ORIGINS "https://agent.example.com,http://localhost:4200"' 'hub-api MCP_ENABLED "false"')"
expect_invalid() {
local output
if output="$(helm template hub "$CHART_DIR" "$@" 2>&1)"; then
echo "Invalid MCP settings unexpectedly rendered: $*" >&2
return 1
fi
if [[ "$output" != *"mcp"* ]]; then
printf 'Rendering failed for an unexpected reason:\n%s\n' "$output" >&2
return 1
fi
}
expect_invalid --set-string kerberoshub.api.mcp.enabled=true
expect_invalid --set-string kerberoshub.api.mcp.allowedOrigins=https://agent.example.com
expect_invalid --set-string kerberoshub.api.mcp.origin=https://agent.example.com
for origin in 'https://*.example.com' 'https://agent.example.com/' \
'https://agent.example.com/path' 'https://agent.example.com?query' \
'https://agent.example.com#fragment' 'https://user@agent.example.com' \
'https://agent.example.com,https://other.example.com' 'https://bad host' 'ftp://agent.example.com'; do
expect_invalid --set-json "kerberoshub.api.mcp.allowedOrigins=[\"$origin\"]"
done
expect_invalid --set-json 'kerberoshub.api.mcp.allowedOrigins=["https://agent.example.com","https://agent.example.com"]'
echo "All Hub MCP configuration checks passed."