Skip to content

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.