Skip to content

Commit d7c1511

Browse files
authored
feat: add Kubernetes manifests and Tiltfile integration for MCP server (#1209)
* feat: add Kubernetes manifests and Tiltfile integration for MCP server Creates k8s manifests (configmap, secret, deployment, service) for the MCP server in services/mcp-server/k8s/ and adds docker_build, k8s_yaml, and k8s_resource entries to the Tiltfile for local development. The MCP server runs SSE transport on port 8090, connects to the gateway for all backend gRPC calls, and port-forwards on 18090 locally to avoid conflict with the gateway's 8090 port-forward. * fix: use TCP socket probes for MCP server health checks The /sse endpoint is a long-lived SSE stream (it never sends a 200 and returns), making it unsuitable as an httpGet probe target. Switch to tcpSocket probes which verify the port is accepting connections without coupling health checks to transport semantics. * fix: correct labels and add gateway init container in mcp-server manifests - Change component label from mcp-gateway to mcp-server across all manifests - Change tier label from frontend to backend in deployment.yaml (MCP server is a backend API adapter, not a frontend component) - Add wait-for-gateway init container to ensure gateway is ready before the MCP server starts (complements the existing wait-for-control-plane check) * fix: add readOnlyRootFilesystem to init container security contexts Harden init container security contexts to match the main container by adding readOnlyRootFilesystem: true. The busybox nc command does not require a writable root filesystem. --------- Co-authored-by: Ben Coombs <bjcoombs@users.noreply.github.com>
1 parent 7bd1f8c commit d7c1511

5 files changed

Lines changed: 266 additions & 0 deletions

File tree

Tiltfile

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,49 @@ k8s_resource(
613613
labels=['gateway'],
614614
)
615615

616+
# =============================================================================
617+
# MCP Server
618+
# =============================================================================
619+
# Model Context Protocol server for AI agent integration.
620+
# Exposes Meridian's transaction engine capabilities via MCP SSE transport.
621+
# Connects to the gateway for all backend gRPC service calls.
622+
623+
# Standard build args for mcp-server
624+
mcp_server_build_args = {
625+
'VERSION': 'dev',
626+
'COMMIT': local('git rev-parse --short HEAD'),
627+
'BUILD_DATE': get_build_date(),
628+
}
629+
630+
# Build mcp-server Docker image
631+
docker_build(
632+
'mcp-server',
633+
context='.',
634+
dockerfile='services/mcp-server/cmd/Dockerfile',
635+
build_args=mcp_server_build_args,
636+
)
637+
638+
# Deploy mcp-server K8s manifests
639+
k8s_yaml('services/mcp-server/k8s/secret.yaml')
640+
k8s_yaml('services/mcp-server/k8s/configmap.yaml')
641+
k8s_yaml('services/mcp-server/k8s/deployment.yaml')
642+
k8s_yaml('services/mcp-server/k8s/service.yaml')
643+
644+
# Configure mcp-server resource
645+
k8s_resource(
646+
'mcp-server',
647+
port_forwards=['18090:8090'], # MCP SSE endpoint (18090 to avoid conflict with gateway)
648+
resource_deps=[
649+
'generate-proto', # Ensures proto files are generated before building
650+
'gateway', # MCP server routes all gRPC calls through the gateway
651+
'control-plane', # Wait for control-plane readiness (init container check)
652+
],
653+
labels=['gateway'],
654+
objects=[
655+
'mcp-server-config:configmap',
656+
],
657+
)
658+
616659
# =============================================================================
617660
# Resource Configuration
618661
# =============================================================================
@@ -948,6 +991,9 @@ Gateway:
948991
• HTTP Gateway → localhost:8090 (subdomain routing)
949992
- Tenant resolution via TenantResolverMiddleware
950993
- Proxies to gRPC backends via Connect protocol
994+
• MCP Server → localhost:18090 (SSE transport)
995+
- Model Context Protocol for AI agent integration
996+
- Routes all gRPC calls through the HTTP gateway
951997
952998
Frontend:
953999
• Meridian Console → http://localhost:5173 (Vite + React)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: mcp-server-config
5+
labels:
6+
app: mcp-server
7+
component: mcp-server
8+
data:
9+
# Logging configuration
10+
LOG_LEVEL: "info"
11+
12+
# MCP transport configuration
13+
MCP_TRANSPORT: "sse"
14+
MCP_SSE_PORT: "8090"
15+
MCP_SERVER_NAME: "meridian-mcp"
16+
17+
# Meridian gateway address (single gRPC endpoint for all backend services)
18+
MERIDIAN_API_URL: "gateway:8080"
19+
20+
# OpenTelemetry configuration
21+
OTEL_SERVICE_NAME: "mcp-server"
22+
OTEL_SAMPLING_RATE: "0.1"
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# Port Configuration: See shared/platform/ports/ports.go for canonical port assignments.
2+
# This service uses port 8090 for SSE transport (MCP_SSE_PORT).
3+
apiVersion: apps/v1
4+
kind: Deployment
5+
metadata:
6+
name: mcp-server
7+
labels:
8+
app: mcp-server
9+
component: mcp-server
10+
tier: backend
11+
spec:
12+
replicas: 1
13+
selector:
14+
matchLabels:
15+
app: mcp-server
16+
strategy:
17+
type: RollingUpdate
18+
rollingUpdate:
19+
maxSurge: 1
20+
maxUnavailable: 0
21+
template:
22+
metadata:
23+
labels:
24+
app: mcp-server
25+
component: mcp-server
26+
tier: backend
27+
spec:
28+
securityContext:
29+
runAsNonRoot: true
30+
runAsUser: 65532
31+
runAsGroup: 65532
32+
fsGroup: 65532
33+
seccompProfile:
34+
type: RuntimeDefault
35+
initContainers:
36+
- name: wait-for-control-plane
37+
image: busybox:1.36
38+
command:
39+
- sh
40+
- -c
41+
- |
42+
echo 'Waiting for control-plane...'
43+
until nc -z control-plane 50062; do
44+
echo 'control-plane not ready, retrying in 2s'
45+
sleep 2
46+
done
47+
echo 'control-plane is ready'
48+
resources:
49+
requests:
50+
cpu: 10m
51+
memory: 16Mi
52+
limits:
53+
cpu: 50m
54+
memory: 32Mi
55+
securityContext:
56+
allowPrivilegeEscalation: false
57+
readOnlyRootFilesystem: true
58+
runAsNonRoot: true
59+
runAsUser: 65532
60+
capabilities:
61+
drop:
62+
- ALL
63+
- name: wait-for-gateway
64+
image: busybox:1.36
65+
command:
66+
- sh
67+
- -c
68+
- |
69+
echo 'Waiting for gateway...'
70+
until nc -z gateway 8080; do
71+
echo 'gateway not ready, retrying in 2s'
72+
sleep 2
73+
done
74+
echo 'gateway is ready'
75+
resources:
76+
requests:
77+
cpu: 10m
78+
memory: 16Mi
79+
limits:
80+
cpu: 50m
81+
memory: 32Mi
82+
securityContext:
83+
allowPrivilegeEscalation: false
84+
readOnlyRootFilesystem: true
85+
runAsNonRoot: true
86+
runAsUser: 65532
87+
capabilities:
88+
drop:
89+
- ALL
90+
containers:
91+
- name: mcp-server
92+
image: mcp-server:latest
93+
imagePullPolicy: IfNotPresent
94+
ports:
95+
- name: sse
96+
containerPort: 8090
97+
protocol: TCP
98+
envFrom:
99+
- configMapRef:
100+
name: mcp-server-config
101+
env:
102+
- name: MERIDIAN_API_KEY
103+
valueFrom:
104+
secretKeyRef:
105+
name: mcp-server-secrets
106+
key: MERIDIAN_API_KEY
107+
- name: POD_NAME
108+
valueFrom:
109+
fieldRef:
110+
fieldPath: metadata.name
111+
- name: POD_NAMESPACE
112+
valueFrom:
113+
fieldRef:
114+
fieldPath: metadata.namespace
115+
- name: POD_IP
116+
valueFrom:
117+
fieldRef:
118+
fieldPath: status.podIP
119+
resources:
120+
requests:
121+
cpu: 50m
122+
memory: 64Mi
123+
limits:
124+
cpu: 200m
125+
memory: 256Mi
126+
livenessProbe:
127+
tcpSocket:
128+
port: sse
129+
initialDelaySeconds: 10
130+
periodSeconds: 10
131+
timeoutSeconds: 3
132+
successThreshold: 1
133+
failureThreshold: 3
134+
readinessProbe:
135+
tcpSocket:
136+
port: sse
137+
initialDelaySeconds: 5
138+
periodSeconds: 5
139+
timeoutSeconds: 3
140+
successThreshold: 1
141+
failureThreshold: 3
142+
startupProbe:
143+
tcpSocket:
144+
port: sse
145+
initialDelaySeconds: 0
146+
periodSeconds: 2
147+
timeoutSeconds: 3
148+
successThreshold: 1
149+
failureThreshold: 30
150+
securityContext:
151+
allowPrivilegeEscalation: false
152+
readOnlyRootFilesystem: true
153+
runAsNonRoot: true
154+
runAsUser: 65532
155+
capabilities:
156+
drop:
157+
- ALL
158+
volumeMounts:
159+
- name: tmp
160+
mountPath: /tmp
161+
volumes:
162+
- name: tmp
163+
emptyDir: {}
164+
terminationGracePeriodSeconds: 30
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# SECURITY NOTE: This secret contains development credentials only.
2+
# For production deployments:
3+
# - Use external secret management (e.g., Sealed Secrets, External Secrets Operator)
4+
# - Rotate credentials regularly
5+
# - Use RBAC to restrict secret access
6+
# - Enable encryption at rest for secrets
7+
apiVersion: v1
8+
kind: Secret
9+
metadata:
10+
name: mcp-server-secrets
11+
labels:
12+
app: mcp-server
13+
type: Opaque
14+
stringData:
15+
# Local development API key - replace with a real key in production
16+
MERIDIAN_API_KEY: "dev-mcp-api-key"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Port Configuration: See shared/platform/ports/ports.go for canonical port assignments.
2+
# This service uses port 8090 for SSE transport (MCP_SSE_PORT).
3+
apiVersion: v1
4+
kind: Service
5+
metadata:
6+
name: mcp-server
7+
labels:
8+
app: mcp-server
9+
component: mcp-server
10+
spec:
11+
type: ClusterIP
12+
ports:
13+
- name: sse
14+
port: 8090
15+
targetPort: sse
16+
protocol: TCP
17+
selector:
18+
app: mcp-server

0 commit comments

Comments
 (0)