Troubleshoot an Exposition or Gateway
Use this guide when an Exposition has no endpoint, a Gateway does not receive a change, or an MCP request fails. Start with the first failing layer and stop when its recovery check succeeds.
Prerequisites​
You need:
- reShapr runtime
0.2.3and, for Kubernetes-managed resources, controllers0.0.1; reshapr logincompleted for the affected organization;- access to Gateway and operator logs;
curl,jq, andkubectlwhen the workload runs on Kubernetes;- the Exposition ID, expected Gateway Group, Gateway labels, and MCP URL.
Set the values relevant to your deployment:
export EXPOSITION_ID='<exposition-id>'
export MCP_URL='https://<gateway-host>/mcp/<organization>/<exposition-name>'
export APP_NAMESPACE='<application-namespace>'
export PROXY_NAMESPACE='<gateway-namespace>'
export PLATFORM_NAMESPACE='reshapr-system'
Choose the failing layer​
Run these checks in order:
reshapr expo get "${EXPOSITION_ID}"must show the expected Gateway Group and at least one endpoint.- The Gateway readiness endpoint must return
UP. - A
server/discoverrequest must reach the expected Exposition. tools/listmust contain the expected Tool.- A read-only
tools/callmust reach and be accepted by the backend.
An Exposition can be ready in the control plane while no running Gateway matches its group. Gateway readiness can also be UP while a particular Exposition is absent. Keep these checks separate.
Exposition has no active endpoint​
Inspect the Exposition and its target group:
reshapr expo get "${EXPOSITION_ID}"
reshapr gateway-group list
If no endpoint is listed, compare the target Gateway Group labels with the labels advertised by the intended Gateway. For a Kubernetes Gateway, inspect the rendered environment:
kubectl get deployment/reshapr-proxy \
--namespace "${PROXY_NAMESPACE}" \
--output json \
| jq -r '.spec.template.spec.containers[0].env[]
| select(.name == "RESHAPR_GATEWAY_LABELS")
| .value'
For a standalone container, inspect its startup configuration or logs. A Gateway must advertise labels compatible with the target group. Labels are selection criteria; a mismatch does not set the Exposition or GatewayGroup CR to ERROR.
Correct either the Gateway labels or the intended Gateway Group, roll out the affected workload, and wait for registration. Then repeat:
reshapr expo get "${EXPOSITION_ID}"
Recovery is complete when the expected Gateway hostname appears in ENDPOINTS.
Kubernetes resource is not ready​
When controllers manage the Exposition, inspect every dependency and compare desired and observed generations:
kubectl get services.reshapr.io,gatewaygroups.reshapr.io,configurationplans.reshapr.io,expositions.reshapr.io,secretsources.reshapr.io \
--namespace "${APP_NAMESPACE}" \
--output json | jq -r '
.items[] |
[.kind, .metadata.name, .status.status, .metadata.generation,
.status.observedGeneration, (.status.message // "")] |
@tsv'
For Service, GatewayGroup, ConfigurationPlan, Exposition, and SecretSource, require both:
status.statusisREADY;status.observedGenerationequalsmetadata.generation.
Use status.message to resolve an ERROR. An Exposition in IN_PROGRESS commonly waits for its Service, ConfigurationPlan, or GatewayGroup to exist remotely. Correct the named dependency first.
CustomTools and Resource use a different status shape in controllers 0.0.1:
kubectl get customtools.reshapr.io,resources.reshapr.io \
--namespace "${APP_NAMESPACE}" \
--output json | jq -r '
.items[] |
[.kind, .metadata.name, .status.state, (.status.message // "")] |
@tsv'
If the status does not explain the failure, inspect the operator logs:
kubectl logs \
--namespace "${PLATFORM_NAMESPACE}" \
--selector app.kubernetes.io/component=operator \
--tail 200
After correcting the resource, rerun the first status command. Recovery requires READY with matching generations, followed by an endpoint in reshapr expo get.
Gateway is not ready or registered​
Inspect the workload before changing registration settings:
kubectl get pods --namespace "${PROXY_NAMESPACE}"
kubectl logs deployment/reshapr-proxy \
--namespace "${PROXY_NAMESPACE}" \
--tail 200
Check, in this order:
- DNS and TCP reachability from the Gateway to the configured control-plane host and port;
- whether the control-plane connection expects TLS or plaintext;
- the presence and validity of the Gateway API token;
- uniqueness of the Gateway ID among running instances;
- syntactic correctness of advertised FQDNs and labels.
Port-forward the Gateway service and query readiness locally:
kubectl port-forward \
--namespace "${PROXY_NAMESPACE}" \
service/reshapr-proxy 7777:7777
In another terminal:
curl --fail --silent http://localhost:7777/q/health/ready | jq -er '.status'
Recovery requires UP, followed by the expected endpoint in reshapr expo get. Readiness proves initial control-plane connectivity, not backend reachability.
Gateways advertise health every two minutes. The control plane considers a registration stale after five minutes without an advertisement, and cleanup runs periodically. Do not use the stale-registration window as a readiness test.
Gateway did not receive a recent change​
First verify that the Kubernetes generation, when applicable, has been observed and that the control-plane representation has the expected values. Then inspect Gateway logs for change-stream or re-registration errors:
kubectl logs deployment/reshapr-proxy \
--namespace "${PROXY_NAMESPACE}" \
--since 15m
An initialized Gateway keeps the last configuration it fetched while synchronization is unavailable. During that interval, new or updated Expositions can be absent and deleted ones can remain locally.
Restore Gateway-to-control-plane connectivity and wait for stream retry or health-triggered re-registration. If the process cannot recover, perform a controlled rollout after preserving its logs:
kubectl rollout restart deployment/reshapr-proxy \
--namespace "${PROXY_NAMESPACE}"
kubectl rollout status deployment/reshapr-proxy \
--namespace "${PROXY_NAMESPACE}" \
--timeout 5m
Recovery requires readiness UP and a fresh server/discover or tools/list response showing the changed MCP surface.
MCP request returns 400, 401, 403, or 404​
Capture the HTTP status, headers, and JSON-RPC body together:
curl --include --silent --show-error \
--header 'Content-Type: application/json' \
--header 'Accept: application/json, text/event-stream' \
--header 'MCP-Protocol-Version: 2026-07-28' \
--header 'Mcp-Method: server/discover' \
--data '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-troubleshooting","version":"0.2.3"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \
"${MCP_URL}"
Use the status and returned error together:
| Status | Check | Recovery check |
|---|---|---|
400 | Validate JSON-RPC and the protocol version. For MCP 2026-07-28, make Mcp-Method, optional Mcp-Name, and the request body agree. | server/discover returns result.supportedVersions. |
401 | Add the configured x-reshapr-key or bearer token. For OAuth, check expiry, signature, issuer, and required claims. | The same request succeeds with a valid credential. |
403 | For OAuth, compare the token resource and service claims with the endpoint, then check the Exposition scopes. | A token with the correct resource and scope succeeds. |
404 | Check the organization and Exposition path, then verify the requested method exists in the selected protocol mode. | server/discover and tools/list find the intended surface. |
For an audited Configuration Plan, search the exported audit logs for event.action=authentication. Its event.reason distinguishes failures such as invalid_api_key, missing_bearer_token, invalid_token, or missing_scope.
Tool reaches reShapr but the backend fails​
A Tool execution failure normally appears in the JSON-RPC result rather than as an endpoint-authentication status. Inspect the full result and correlate it with Gateway logs:
kubectl logs deployment/reshapr-proxy \
--namespace "${PROXY_NAMESPACE}" \
--since 5m
Check the configured backend URL, DNS, egress policy, TLS trust, backend timeout, and backend credential independently. A Gateway-generated 504 result indicates that the backend exceeded the configured timeout. Other backend HTTP statuses can be propagated into the Tool result.
Run a direct request from an approved diagnostic workload in the same network boundary when policy permits. Do not print credentials or weaken TLS to make the test pass.
Recovery requires a read-only tools/call whose result is not marked isError: true and whose expected backend content is present.
SecretSource cannot resolve a credential​
Inspect the aggregate state and each per-Secret condition:
kubectl get secretsource '<secret-source-name>' \
--namespace "${APP_NAMESPACE}" \
--output json \
| jq '{status: .status.status,
generation: .metadata.generation,
observed: .status.observedGeneration,
message: .status.message,
conditions: .status.conditions}'
For a secretRef, confirm that the Kubernetes Secret exists in the same namespace and contains every named key:
kubectl get secret '<kubernetes-secret-name>' \
--namespace "${APP_NAMESPACE}" \
--output json \
| jq '.data | keys'
Do not decode or log the values. Confirm that the operator has its Secret-reader RBAC, then correct the reference or missing key. A Kubernetes Secret update triggers SecretSource reconciliation.
Recovery requires the SecretSource to be READY with matching generations and successful per-Secret conditions. Finish with a read-only Tool call because reconciliation does not prove that the backend accepts the credential.
Tool call is waiting for elicitation​
For stateless MCP 2026-07-28, an expected elicitation response has result.resultType=input_required, one or more elicitation/create URLs, and an opaque requestState. This is not a Gateway outage.
Use a compatible MCP client to open the returned URL over trusted TLS, complete the credential or OAuth flow, and resume the Tool call while preserving requestState. Stateless elicitation requires an OAuth-protected Exposition because reShapr associates the value with the token's iss and sub claims.
If the request instead returns 400 for a missing client capability, use a client that declares elicitation support. Clients using a protocol before 2026-07-28 receive the URL_ELICITATION_REQUIRED JSON-RPC error and bind the value to their MCP session.
When a previously working elicited credential causes a backend 401, reShapr evicts it. Complete elicitation again with a current credential. Recovery requires the resumed Tool call to return expected backend content.
Result​
The first failing layer now has an observable recovery check: reconciled desired state, a matching and ready Gateway, a synchronized MCP surface, accepted endpoint credentials, or a successful backend call.
Limits​
- A
READYcustom resource proves control-plane reconciliation, not Gateway selection, synchronization, ingress, or backend health. - Gateway readiness proves initial control-plane connectivity, not that every Exposition is loaded or every backend is reachable.
- Release
0.2.3retains the last fetched local registry during synchronization loss; this is not an offline-operation guarantee. - Controllers
0.0.1do not expose one uniform status contract for all custom resources. - Logs and telemetry depend on the deployment's collection and retention configuration.
Next step​
Use Observe the reShapr Gateway to export the signals used here. Review Control Plane to Gateway Synchronization for the registration, streaming, heartbeat, and recovery model.
The release-tagged reShapr runtime and controllers documentation remain the canonical behavioral references.