Skip to content

Commit f1b14df

Browse files
committed
feat(metamcp/STDIO): support per-server env and stdioSecretEnv (resolved from K8s Secrets at provision time); docs + schema; bump 0.1.14
1 parent c774917 commit f1b14df

File tree

5 files changed

+82
-4
lines changed

5 files changed

+82
-4
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.1.13
5+
version: 0.1.14
66
appVersion: "latest"
77
icon: https://icoretech.github.io/helm/charts/metamcp/logo.png
88
keywords:

charts/metamcp/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,23 @@ Provisioning authentication
193193
- Minimal e2e (in-cluster URLs, no Ingress): `examples/e2e.yaml`
194194
- Cache PVC per server (requires default StorageClass): `examples/provision-pvc.yaml`
195195
- Advanced options (resources, HPA, probes, volumes, init containers): `examples/provision-advanced.yaml`
196+
# STDIO server with env and Secret-backed env
197+
198+
provision:
199+
enabled: true
200+
servers:
201+
- name: figma
202+
type: STDIO
203+
command: "npx"
204+
args: ["-y", "figma-developer-mcp", "--stdio"]
205+
# Plain env (non-secret)
206+
env:
207+
LOG_LEVEL: debug
208+
# Secret-backed env: the provision Job reads Secret/<name> key=<key> and sets VAR=value when creating the server
209+
stdioSecretEnv:
210+
FIGMA_API_KEY:
211+
name: figma-mcp-env
212+
key: FIGMA_API_KEY
213+
FIGMA_PERSONAL_ACCESS_TOKEN:
214+
name: figma-mcp-env
215+
key: FIGMA_PERSONAL_ACCESS_TOKEN

charts/metamcp/README.md.gotmpl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,23 @@ Provisioning authentication
135135
- Minimal e2e (in-cluster URLs, no Ingress): `examples/e2e.yaml`
136136
- Cache PVC per server (requires default StorageClass): `examples/provision-pvc.yaml`
137137
- Advanced options (resources, HPA, probes, volumes, init containers): `examples/provision-advanced.yaml`
138+
# STDIO server with env and Secret-backed env
139+
140+
provision:
141+
enabled: true
142+
servers:
143+
- name: figma
144+
type: STDIO
145+
command: "npx"
146+
args: ["-y", "figma-developer-mcp", "--stdio"]
147+
# Plain env (non-secret)
148+
env:
149+
LOG_LEVEL: debug
150+
# Secret-backed env: the provision Job reads Secret/<name> key=<key> and sets VAR=value when creating the server
151+
stdioSecretEnv:
152+
FIGMA_API_KEY:
153+
name: figma-mcp-env
154+
key: FIGMA_API_KEY
155+
FIGMA_PERSONAL_ACCESS_TOKEN:
156+
name: figma-mcp-env
157+
key: FIGMA_PERSONAL_ACCESS_TOKEN

charts/metamcp/scripts/provision.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import http.cookiejar as cookiejar
44
import socket
55
from urllib.parse import urlparse
6+
import base64
67

78
def log(msg):
89
print(f"[provision] {msg}", flush=True)
@@ -160,7 +161,19 @@ def trpc_post_batch(path, body):
160161
body['command'] = cmd
161162
if isinstance(args, list) and args:
162163
body['args'] = args
163-
if s.get('env'): body['env'] = s['env']
164+
env_map = {}
165+
if s.get('env') and isinstance(s['env'], dict):
166+
env_map.update({k:str(v) for k,v in s['env'].items()})
167+
# Resolve stdioSecretEnv: { VAR: { name: <secret>, key: <key> (defaults to VAR) } }
168+
sec = s.get('stdioSecretEnv')
169+
if sec and isinstance(sec, dict):
170+
for var, ref in sec.items():
171+
if isinstance(ref, dict) and ref.get('name'):
172+
val = k8s_get_secret_val(ref['name'], ref.get('key') or var)
173+
if val is not None:
174+
env_map[var] = val
175+
if env_map:
176+
body['env'] = env_map
164177
r = trpc_post('/trpc/frontend/frontend.mcpServers.create', body)
165178
if r.ok:
166179
try:
@@ -294,3 +307,18 @@ def list_servers():
294307
trpc_post('/trpc/frontend/frontend.mcpServers.update', payload)
295308
except Exception:
296309
pass
310+
def k8s_get_secret_val(name: str, key: str):
311+
try:
312+
token = open('/var/run/secrets/kubernetes.io/serviceaccount/token','r').read().strip()
313+
cacert = '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'
314+
ns = NS
315+
url = f"https://kubernetes.default.svc/api/v1/namespaces/{ns}/secrets/{name}"
316+
r = requests.get(url, headers={'Authorization': f'Bearer {token}'}, verify=cacert, timeout=5)
317+
if r.status_code == 200:
318+
data = r.json().get('data',{})
319+
b = data.get(key)
320+
if b:
321+
return base64.b64decode(b).decode()
322+
except Exception:
323+
pass
324+
return None

charts/metamcp/values.schema.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@
4848
"extraEnv": { "type": "array", "items": { "type": "object" } },
4949
"envFrom": { "type": "array", "items": { "type": "object" } },
5050
"secretEnv": { "type": "object", "additionalProperties": { "type": "string" } }
51+
,"stdioSecretEnv": {
52+
"type": "object",
53+
"additionalProperties": {
54+
"type": "object",
55+
"properties": {
56+
"name": { "type": "string" },
57+
"key": { "type": "string" }
58+
},
59+
"required": ["name"]
60+
}
61+
}
5162
,"port": { "type": ["integer","null"], "minimum": 1 }
5263
,"resources": { "type": "object" }
5364
,"securityContext": { "type": "object" }
@@ -158,8 +169,7 @@
158169
},
159170
"allOf": [
160171
{ "if": { "properties": { "type": { "const": "STDIO" } } }, "then": { "required": ["command"] } },
161-
{ "if": { "properties": { "type": { "const": "STDIO" } } }, "then": { "not": { "required": ["cache"] } } },
162-
{ "if": { "properties": { "type": { "const": "STDIO" } } }, "then": { "not": { "anyOf": [ { "required": ["envFrom"] }, { "required": ["secretEnv"] } ] } } }
172+
{ "if": { "properties": { "type": { "const": "STDIO" } } }, "then": { "not": { "required": ["cache"] } } }
163173
]
164174
}
165175
},

0 commit comments

Comments
 (0)