Service Iteration¶
After setup --test-mode succeeds, do not rerun full setup for every code
change. Run the targeted tests, rebuild the image for the changed component, and
roll only that Deployment.
Test First¶
Use focused tests while iterating:
go test ./internal/operator/... ./internal/cli/... -count=1
go test ./internal/agentadapter -count=1
(cd services/api && go test ./... -count=1)
(cd services/ui && go test ./... -count=1)
node --check services/ui/static/app.js
Run wider checks before handing off a broad change:
gofmt -s -l .
go vet ./...
go test ./... -count=1
git diff --check
API and UI¶
API and UI changes often need to roll together because the browser talks to the API through the UI service. The UI forwards origin headers so connect configs can point at the reachable local MCP route.
Build and roll the UI:
SERVICE=ui
IMAGE_REPO=mcp-sentinel-ui
DOCKERFILE=services/ui/Dockerfile
BUILD_CONTEXT=.
DEPLOYMENT=mcp-sentinel-ui
CONTAINER=ui
TAG="${SERVICE}-dev-$(date +%s)"
LOCAL_IMAGE="${IMAGE_REPO}:${TAG}"
REGISTRY=registry.registry.svc.cluster.local:5000
docker build -t "$LOCAL_IMAGE" -f "$DOCKERFILE" "$BUILD_CONTEXT"
./bin/mcp-runtime auth login --api-url http://localhost:18080
./bin/mcp-runtime registry push \
--image "$LOCAL_IMAGE" \
--name "$IMAGE_REPO"
kubectl -n mcp-sentinel set image \
"deployment/$DEPLOYMENT" \
"$CONTAINER=$REGISTRY/$IMAGE_REPO:$TAG"
kubectl -n mcp-sentinel rollout status "deployment/$DEPLOYMENT" --timeout=90s
Use the same shape for API:
| Variable | Value |
|---|---|
SERVICE |
api |
IMAGE_REPO |
mcp-sentinel-api |
DOCKERFILE |
services/api/Dockerfile |
BUILD_CONTEXT |
. |
DEPLOYMENT |
mcp-sentinel-api |
CONTAINER |
api |
Use a new tag for every build. Reusing latest with IfNotPresent can leave
old images cached on the node.
Ingest and Processor¶
For analytics pipeline changes:
| Service | Image repo | Dockerfile | Build context | Deployment | Container |
|---|---|---|---|---|---|
| Ingest | mcp-sentinel-ingest |
services/ingest/Dockerfile |
. |
mcp-sentinel-ingest |
ingest |
| Processor | mcp-sentinel-processor |
services/processor/Dockerfile |
. |
mcp-sentinel-processor |
processor |
After rolling either service, generate one MCP request and check both logs (admin kubectl):
./bin/mcp-runtime sentinel logs ingest --since 10m
./bin/mcp-runtime sentinel logs processor --since 10m
./bin/mcp-runtime sentinel events
Operator¶
Operator changes affect how MCPServer, MCPAccessGrant, and
MCPAgentSession objects reconcile. Run the operator tests first:
go test ./internal/operator/... -count=1
For a local Kind-only debug build, build an image, load it into the Kind node, and roll the operator:
TAG="operator-dev-$(date +%s)"
IMAGE="registry.registry.svc.cluster.local:5000/mcp-runtime-operator:${TAG}"
DOCKER_BUILDKIT=0 docker build --platform=linux/$(go env GOARCH) \
-t "$IMAGE" \
-f Dockerfile.operator .
kind load docker-image "$IMAGE" --name mcp-runtime
kubectl patch deployment/mcp-runtime-operator-controller-manager \
-n mcp-runtime \
--type=json \
-p='[{"op":"replace","path":"/spec/template/spec/containers/0/imagePullPolicy","value":"IfNotPresent"}]'
kubectl set image deployment/mcp-runtime-operator-controller-manager \
-n mcp-runtime \
manager="$IMAGE"
kubectl rollout status deployment/mcp-runtime-operator-controller-manager \
-n mcp-runtime \
--timeout=180s
Then watch reconciliation:
kubectl logs -n mcp-runtime deploy/mcp-runtime-operator-controller-manager --since=10m
kubectl get mcpservers -A
kubectl get deploy,svc,ingress -A | rg '<server-name>|mcp-runtime'
If you changed CRD fields, apply the CRD before expecting old controllers or new controllers to agree on validation:
kubectl apply -f config/crd/bases/mcpruntime.org_mcpservers.yaml
MCP Gateway Sidecar¶
services/mcp-gateway runs as the mcp-gateway sidecar in each MCP server pod.
To test gateway changes, rebuild and push mcp-sentinel-mcp-gateway, update the
operator's MCP_GATEWAY_PROXY_IMAGE, roll the operator, then restart affected
MCP server pods so the sidecar is reinjected.
Check the gateway sidecar:
POD="$(kubectl get pods -n <namespace> -l app=<server-name> -o jsonpath='{.items[0].metadata.name}')"
kubectl logs -n <namespace> "$POD" -c mcp-gateway
kubectl describe pod -n <namespace> "$POD"
Registry Notes¶
Inside Kubernetes, image references use
registry.registry.svc.cluster.local:5000. Your host usually cannot resolve
that DNS name. Prefer mcp-runtime registry push, which uses an in-cluster
helper after platform login, or kind load docker-image for single-node Kind
debugging.
Raw docker push registry.registry.svc.cluster.local:5000/... from the host is
expected to fail unless you have added host DNS and insecure registry settings.