Skip to content

Commit 9a55ba6

Browse files
committed
feat(metamcp): support secret-backed remote urls
1 parent e51ff4d commit 9a55ba6

File tree

9 files changed

+94
-32
lines changed

9 files changed

+94
-32
lines changed

charts/metamcp/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: v2
22
name: metamcp
33
description: MetaMCP aggregator Helm chart for Kubernetes
44
type: application
5-
version: 0.3.4
5+
version: 0.3.5
66
appVersion: "2.4.22"
77
icon: https://icoretech.github.io/helm/charts/metamcp/logo.png
88
keywords:

charts/metamcp/README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Declare everything under `provision.*`:
3838
- Servers:
3939
- `type: STDIO` → MetaMCP spawns the process inside its container using `command` + `args` (+ optional `env`). Ensure the MetaMCP image contains the required runtime (e.g., Node/PNPM/NPM or Python/uv).
4040
- `type: STREAMABLE_HTTP` or `SSE`:
41-
- remote: provide `url` (no Pod is created here) + optional `bearerToken`/`headers`.
41+
- remote: provide `url` or `urlFrom` (no Pod is created here) + optional `bearerToken`/`headers`.
4242
- deploy: provide one of `node`/`python`/`image` and optionally `port` (defaults to `3001`);
4343
the chart creates a Deployment/Service and auto‑derives the URL for registration.
4444
- Namespaces: group servers by name.
@@ -228,6 +228,19 @@ provision:
228228
name: metamcp-icoretech-airbroke-headers
229229
```
230230

231+
Secret-backed remote URL (Windmill):
232+
233+
```yaml
234+
provision:
235+
enabled: true
236+
servers:
237+
- name: windmill-icoretech
238+
type: SSE
239+
urlFrom:
240+
- secretRef:
241+
name: metamcp-windmill-icoretech-url
242+
```
243+
231244
## Configuration reference
232245

233246
<!-- markdownlint-disable MD013 -->

charts/metamcp/README.md.gotmpl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Declare everything under `provision.*`:
3838
- Servers:
3939
- `type: STDIO` → MetaMCP spawns the process inside its container using `command` + `args` (+ optional `env`). Ensure the MetaMCP image contains the required runtime (e.g., Node/PNPM/NPM or Python/uv).
4040
- `type: STREAMABLE_HTTP` or `SSE`:
41-
- remote: provide `url` (no Pod is created here) + optional `bearerToken`/`headers`.
41+
- remote: provide `url` or `urlFrom` (no Pod is created here) + optional `bearerToken`/`headers`.
4242
- deploy: provide one of `node`/`python`/`image` and optionally `port` (defaults to `3001`);
4343
the chart creates a Deployment/Service and auto‑derives the URL for registration.
4444
- Namespaces: group servers by name.
@@ -228,6 +228,19 @@ provision:
228228
name: metamcp-icoretech-airbroke-headers
229229
```
230230

231+
Secret-backed remote URL (Windmill):
232+
233+
```yaml
234+
provision:
235+
enabled: true
236+
servers:
237+
- name: windmill-icoretech
238+
type: SSE
239+
urlFrom:
240+
- secretRef:
241+
name: metamcp-windmill-icoretech-url
242+
```
243+
231244
## Configuration reference
232245

233246
<!-- markdownlint-disable MD013 -->
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
users:
2+
- email: admin@example.com
3+
name: Admin
4+
password: change-me
5+
6+
provision:
7+
enabled: true
8+
servers:
9+
- name: windmill-icoretech
10+
type: SSE
11+
urlFrom:
12+
- secretRef:
13+
name: metamcp-windmill-icoretech-url

charts/metamcp/scripts/provision.py

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ def merge_refs(refs):
7373
merged.update({k: str(v) for k, v in k8s_get_configmap_data(src['configMapRef']['name']).items()})
7474
return merged
7575

76+
def resolve_remote_url(server):
77+
desired_url = server.get('url')
78+
if server.get('urlFrom'):
79+
desired_url = merge_refs(server.get('urlFrom') or []).get('url') or desired_url
80+
if desired_url:
81+
return desired_url
82+
base = server.get('serviceBase')
83+
if base:
84+
suffix = '/mcp' if server['type'] == 'STREAMABLE_HTTP' else '/sse'
85+
if base.startswith('http://') or base.startswith('https://'):
86+
return base + suffix
87+
return 'http://' + base + suffix
88+
raise RuntimeError(
89+
f"server {server.get('name')}: remote server requires url, urlFrom[url], or a deployable serviceBase"
90+
)
91+
7692
def load_managed_state():
7793
data = k8s_get_configmap_data(STATE_CONFIGMAP)
7894
state = {key: set() for key in STATE_KEYS}
@@ -374,42 +390,33 @@ def delete_server(name, servers_by_name):
374390
desired_url = None
375391
desired_headers = None
376392
if st in ('SSE','STREAMABLE_HTTP'):
377-
desired_url = s.get('url')
393+
desired_url = resolve_remote_url(s)
378394
header_map = {}
379395
if s.get('headers') and isinstance(s['headers'], dict):
380396
header_map.update({k:str(v) for k,v in s['headers'].items()})
381397
if s.get('headersFrom'):
382398
header_map.update(merge_refs(s.get('headersFrom') or []))
383399
if header_map or s.get('headersFrom'):
384400
desired_headers = header_map
385-
if not desired_url:
386-
base = s.get('serviceBase')
387-
if base:
388-
suffix = '/mcp' if st=='STREAMABLE_HTTP' else '/sse'
389-
# ensure scheme
390-
if base.startswith('http://') or base.startswith('https://'):
391-
desired_url = base + suffix
392-
else:
393-
desired_url = 'http://' + base + suffix
394-
# proactive readiness for deployed servers
395-
if base:
396-
try:
397-
hostport = base.split('://')[-1]
398-
host, port = hostport.split(':')[0], int(hostport.split(':')[1])
399-
except Exception:
400-
host, port = None, None
401-
if host and port:
402-
log(f"[ready] waiting tcp {host}:{port} …")
403-
max_tries = 8
404-
ok = False
405-
for _ in range(max_tries):
406-
try:
407-
with socket.create_connection((host, port), timeout=1.5):
408-
ok = True
409-
break
410-
except Exception:
411-
time.sleep(1)
412-
log(f"[ready] tcp {host}:{port} -> {'ok' if ok else 'timeout'}")
401+
base = s.get('serviceBase')
402+
if base:
403+
try:
404+
hostport = base.split('://')[-1]
405+
host, port = hostport.split(':')[0], int(hostport.split(':')[1])
406+
except Exception:
407+
host, port = None, None
408+
if host and port:
409+
log(f"[ready] waiting tcp {host}:{port} …")
410+
max_tries = 8
411+
ok = False
412+
for _ in range(max_tries):
413+
try:
414+
with socket.create_connection((host, port), timeout=1.5):
415+
ok = True
416+
break
417+
except Exception:
418+
time.sleep(1)
419+
log(f"[ready] tcp {host}:{port} -> {'ok' if ok else 'timeout'}")
413420
if desired_url:
414421
body['url'] = desired_url
415422
if s.get('bearerToken'):

charts/metamcp/scripts/test-render.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,11 @@ if ! rg -q '"headersFrom":\[\{"secretRef":\{"name":"metamcp-icoretech-airbroke-h
2020
sed -n '1,220p' "$OUT" >&2
2121
exit 1
2222
fi
23+
24+
helm template t "$CHART" -f "$ROOT/ci/urlfrom-values.yaml" >"$OUT"
25+
26+
if ! rg -q '"urlFrom":\[\{"secretRef":\{"name":"metamcp-windmill-icoretech-url"\}\}\]' "$OUT"; then
27+
echo "expected provision.json to preserve urlFrom secret refs for remote servers" >&2
28+
sed -n '1,220p' "$OUT" >&2
29+
exit 1
30+
fi

charts/metamcp/templates/provision-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ data:
2121
{{- /* Build server dict for the job, including STDIO env sources */ -}}
2222
{{- $item := dict "name" $name "enabled" (default true $s.enabled) "type" $s.type "url" $s.url "bearerToken" $s.bearerToken "headers" ($s.headers | default (dict)) "command" ($s.command | default "") "args" ($s.args | default (list)) "env" ($s.env | default (dict)) -}}
2323
{{- if (hasKey $s "envFrom") }}{{- $_ := set $item "envFrom" ($s.envFrom | default (list)) -}}{{- end -}}
24+
{{- if (hasKey $s "urlFrom") }}{{- $_ := set $item "urlFrom" ($s.urlFrom | default (list)) -}}{{- end -}}
2425
{{- if (hasKey $s "headersFrom") }}{{- $_ := set $item "headersFrom" ($s.headersFrom | default (list)) -}}{{- end -}}
2526
{{- if $hasDeploy }}{{- $_ := set $item "serviceBase" $svcBase -}}{{- end -}}
2627
{{- $outServers = append $outServers $item -}}

charts/metamcp/values.schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"enabled": { "type": "boolean", "default": true },
8181
"type": { "type": "string", "enum": ["SSE", "STREAMABLE_HTTP", "STDIO"] },
8282
"url": { "type": ["string","null"] },
83+
"urlFrom": { "type": "array", "items": { "type": "object" } },
8384
"bearerToken": { "type": "string" },
8485
"headers": { "type": "object", "additionalProperties": { "type": "string" } },
8586
"headersFrom": { "type": "array", "items": { "type": "object" } },

charts/metamcp/values.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ provision:
154154
# headersFrom:
155155
# - secretRef:
156156
# name: errbit-mcp-headers
157+
# - name: windmill
158+
# enabled: true
159+
# type: SSE
160+
# urlFrom:
161+
# - secretRef:
162+
# name: windmill-mcp-url
157163

158164
# namespaces: list of { name, servers: [<server-names>] }
159165
namespaces: []

0 commit comments

Comments
 (0)