Local Kind and Test Mode¶
Use this flow when you need a full local platform: API, UI, operator, registry, Traefik, Sentinel services, and real MCP ingress routes.
Prerequisites¶
Install Docker, Kind, kubectl, Go, curl, jq, and Python 3. Then build the
CLI:
make deps
make build
Create the Kind Cluster¶
The documented test-mode install emits pod images that use
registry.registry.svc.cluster.local:5000/.... The Kind node needs a matching
containerd mirror before setup runs.
cat > /tmp/mcp-runtime-kind.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.registry.svc.cluster.local:5000"]
endpoint = ["http://127.0.0.1:32000"]
EOF
kind create cluster --name mcp-runtime --config /tmp/mcp-runtime-kind.yaml
kubectl config use-context kind-mcp-runtime
Install MCP Runtime¶
Run preflight checks, then install with the HTTP ingress overlay:
./bin/mcp-runtime bootstrap
MCP_SETUP_WAIT_TIMEOUT=900 \
./bin/mcp-runtime setup --test-mode \
--ingress-manifest config/ingress/overlays/http
Check the platform:
./bin/mcp-runtime status
./bin/mcp-runtime cluster status
./bin/mcp-runtime registry status
./bin/mcp-runtime sentinel status # admin kubectl
./bin/mcp-runtime cluster doctor
Expose the dashboard and MCP routes:
kubectl port-forward -n traefik svc/traefik 18080:8000
Local URLs:
| Surface | URL |
|---|---|
| Platform UI | http://localhost:18080/ |
| Platform API | http://localhost:18080/api |
| MCP route shape | http://localhost:18080/<server-name>/mcp |
Keep the Traefik port-forward running while using the browser or curl.
Seeded Logins¶
setup --test-mode seeds local-only platform logins:
| Role | Password | |
|---|---|---|
| User | test@mcpruntime.org |
test@123 |
| Admin | admin@mcpruntime.org |
admin@123 |
These are controlled by PLATFORM_DEV_* keys in the mcp-sentinel-secrets
Secret. They are for local debugging only.
The shared contributor cluster used for tenant-isolation smoke testing also has these local-only tenant accounts:
| Tenant | Password | |
|---|---|---|
| Tenant A | tenant-a-20260510232145@mcpruntime.org |
TenantA-20260510232145! |
| Tenant B | tenant-b-20260510232145@mcpruntime.org |
TenantB-20260510232145! |
Fresh local clusters only have the test and admin accounts unless you create
tenant teams and users yourself.
Catalog Visibility Checks¶
Anonymous users must not see the MCP catalog:
curl -i http://localhost:18080/api/runtime/servers
Expected status: 401 Unauthorized.
Check the default test user:
rm -f /tmp/mcp-test-user-cookie.txt
curl -sS -c /tmp/mcp-test-user-cookie.txt \
-H 'content-type: application/json' \
-d '{"email":"test@mcpruntime.org","password":"test@123"}' \
http://localhost:18080/auth/login
curl -sS -b /tmp/mcp-test-user-cookie.txt \
http://localhost:18080/api/runtime/servers |
jq '{count: (.servers|length), names: [.servers[] | (.namespace + "/" + .name)]}'
In default tenant mode, signed-in users see MCPs from team namespaces they
belong to only. A setup installed with --platform-mode org instead shows the
shared org catalog from mcp-servers-org, and --platform-mode public shows
the public preview catalog from mcp-servers-public.
Check tenant isolation in the shared contributor cluster:
rm -f /tmp/mcp-tenant-a-cookie.txt
curl -sS -c /tmp/mcp-tenant-a-cookie.txt \
-H 'content-type: application/json' \
-d '{"email":"tenant-a-20260510232145@mcpruntime.org","password":"TenantA-20260510232145!"}' \
http://localhost:18080/auth/login
curl -sS -b /tmp/mcp-tenant-a-cookie.txt \
http://localhost:18080/api/runtime/servers |
jq '{count: (.servers|length), names: [.servers[] | (.namespace + "/" + .name)]}'
curl -sS -o /tmp/mcp-tenant-a-cross.txt -w '%{http_code}\n' \
-b /tmp/mcp-tenant-a-cookie.txt \
'http://localhost:18080/api/runtime/servers?namespace=mcp-team-tenant-b'
Tenant A should see mcp-team-tenant-a, and the explicit Tenant B namespace
read should return 403.
Quick Cluster Inventory¶
kubectl get pods -n mcp-runtime -o wide
kubectl get pods -n mcp-sentinel -o wide
kubectl get mcpservers -A \
-o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,TEAM:.spec.teamID,PATH:.spec.ingressPath,READY:.status.deploymentReady,GW:.status.gatewayReady'
kubectl get mcpaccessgrant,mcpagentsession -A -o wide
kubectl get ingress -A
If you remove a stale test server, remove its matching grants, sessions, and single-purpose analytics Secret too:
kubectl delete mcpagentsession <session-name> -n <namespace> --ignore-not-found
kubectl delete mcpaccessgrant <grant-name> -n <namespace> --ignore-not-found
kubectl delete mcpserver <server-name> -n <namespace> --ignore-not-found
kubectl delete secret <server-name>-analytics-creds -n <namespace> --ignore-not-found