What
Validate and record the transport architecture for the MCP Inspector in #671. The PoC is complete; production hardening remains part of the Tools work.
Implementation draft: #779.
Decision
Replace nginx in the Console Plugin image with a small Go server. The same process serves the compiled frontend and a narrow MCP relay on port 9443.
The Kuadrant Operator continues to own the Deployment, Service, serving certificate and ConsolePlugin resource. The relay does not run in the operator process. Its source and binary live in kuadrant-console-plugin, beside the UI that depends on it.
Browser
-> /api/proxy/plugin/kuadrant-console-plugin/backend/
api/mcp/v1/mcpgatewayextensions/{namespace}/{name}
-> OpenShift Console Bridge
injects the signed-in user's OpenShift token
-> Console Plugin Go server
reads the extension and referenced Gateway as that user
derives and validates the target
removes the OpenShift token
adds the separate MCP bearer token, if supplied
-> MCP Gateway
The browser supplies an MCPGatewayExtension namespace and name, never a destination URL.
Why use a backend
MCP Gateways can use any ingress hostname. A finite ConsolePlugin.spec.contentSecurityPolicy.connectSrc list cannot describe every gateway or identity provider in a cluster. Direct browser access also makes gateway CORS part of the feature contract.
The fixed Console proxy keeps browser traffic same-origin. Gateway CORS, dynamic Console CSP entries, custom HTTPRoutes, MCP Gateway PR #1416 and status.mcpEndpoint are not prerequisites.
This revisits two earlier designs:
Keeping the relay in the Console Plugin image reuses the existing Service, certificate and image lifecycle without exposing an application endpoint from the privileged operator process.
First slice
Supported:
initialize and notifications/initialized;
tools/list, including refresh;
tools/call;
- JSON and finite request-scoped SSE responses;
- sessions held in browser memory;
- an optional manually entered bearer token.
Not included: OIDC, Prompts, Logs, open notification streams, server-side session storage or a general-purpose URL proxy.
Target resolution
For each MCP request, the PoC:
- reads the selected
MCPGatewayExtension with the Console-supplied user token;
- requires
Ready=True for the current extension generation;
- reads the Gateway referenced by
spec.targetRef with the same token;
- finds the target listener named by
sectionName;
- takes the host from
spec.publicHost, falling back to the listener hostname;
- takes the scheme and port from the listener and appends
/mcp;
- relays the request through a client that rejects redirects and refuses credentials over plain HTTP unless development mode explicitly allows it.
The backend performs fresh Kubernetes reads for every request and does not cache authorization decisions.
Credential boundary
| Credential |
Used for |
Sent to |
| OpenShift user token |
Extension and Gateway reads |
Kubernetes API only |
| MCP bearer token |
Gateway authentication |
MCP Gateway only |
Console owns the normal Authorization header. The browser therefore sends an optional MCP token in X-Kuadrant-MCP-Authorization. The backend creates a new upstream request and copies only an explicit header allowlist, so the OpenShift token is never implicitly forwarded to the gateway.
The PoC enforces the user's Kubernetes get access. Production should add a separate Inspector use permission so read access alone does not allow unauthenticated gateways to be invoked from the plugin pod's network position.
Operator contract
The operator points both frontend assets and the proxy alias at the existing plugin Service:
spec:
backend:
type: Service
service:
name: kuadrant-console-plugin
namespace: <operator-namespace>
port: 9443
basePath: /
proxy:
- alias: backend
authorization: UserToken
endpoint:
type: Service
service:
name: kuadrant-console-plugin
namespace: <operator-namespace>
port: 9443
oinc is development plumbing only. oinc v0.4.6 includes the standalone Bridge adapter that translates this ConsolePlugin.spec.proxy, configures service-CA trust and reaches the in-cluster Service. The adapter is not part of the production architecture.
Validated result
The PoC ran through the standalone Console in oinc:
- backend health returned
200 through the Console proxy;
initialize returned 200 and preserved Mcp-Session-Id;
notifications/initialized returned 202;
- initial and refreshed
tools/list succeeded;
tools/call reached toystore_greet and returned Hi Ada;
- the live Playwright journey passed with no gateway
connect-src CSP report;
- backend tests cover credential separation and current endpoint derivation;
- a restricted user without extension access received
403 in a separate check.
One limitation: oinc Bridge uses a configured cluster-admin token rather than a real signed-in user's UserToken. Production acceptance still needs a real OpenShift Console test with separate viewer and Inspector users.
Before production
Three areas remain:
- Invocation authorization: add the
use Role, backend review, matching UI check and persona tests.
- Target trust: add strict Kubernetes name checks, target-revision binding, accepted-listener provenance, dial-time address policy, dedicated transports and trusted CA mounts.
- Safe rollout: add probes, limits, certificate reload, graceful draining, a zero-RBAC ServiceAccount, resource watches and staged proxy publication across mixed nginx/Go-server replicas.
Stable error codes, response/concurrency limits, safe metrics and multi-architecture/downstream build checks are also required before release.
Acceptance criteria
What
Validate and record the transport architecture for the MCP Inspector in #671. The PoC is complete; production hardening remains part of the Tools work.
Implementation draft: #779.
Decision
Replace nginx in the Console Plugin image with a small Go server. The same process serves the compiled frontend and a narrow MCP relay on port 9443.
The Kuadrant Operator continues to own the Deployment, Service, serving certificate and
ConsolePluginresource. The relay does not run in the operator process. Its source and binary live inkuadrant-console-plugin, beside the UI that depends on it.The browser supplies an
MCPGatewayExtensionnamespace and name, never a destination URL.Why use a backend
MCP Gateways can use any ingress hostname. A finite
ConsolePlugin.spec.contentSecurityPolicy.connectSrclist cannot describe every gateway or identity provider in a cluster. Direct browser access also makes gateway CORS part of the feature contract.The fixed Console proxy keeps browser traffic same-origin. Gateway CORS, dynamic Console CSP entries, custom HTTPRoutes, MCP Gateway PR #1416 and
status.mcpEndpointare not prerequisites.This revisits two earlier designs:
Keeping the relay in the Console Plugin image reuses the existing Service, certificate and image lifecycle without exposing an application endpoint from the privileged operator process.
First slice
Supported:
initializeandnotifications/initialized;tools/list, including refresh;tools/call;Not included: OIDC, Prompts, Logs, open notification streams, server-side session storage or a general-purpose URL proxy.
Target resolution
For each MCP request, the PoC:
MCPGatewayExtensionwith the Console-supplied user token;Ready=Truefor the current extension generation;spec.targetRefwith the same token;sectionName;spec.publicHost, falling back to the listener hostname;/mcp;The backend performs fresh Kubernetes reads for every request and does not cache authorization decisions.
Credential boundary
Console owns the normal
Authorizationheader. The browser therefore sends an optional MCP token inX-Kuadrant-MCP-Authorization. The backend creates a new upstream request and copies only an explicit header allowlist, so the OpenShift token is never implicitly forwarded to the gateway.The PoC enforces the user's Kubernetes
getaccess. Production should add a separate Inspectorusepermission so read access alone does not allow unauthenticated gateways to be invoked from the plugin pod's network position.Operator contract
The operator points both frontend assets and the proxy alias at the existing plugin Service:
oinc is development plumbing only. oinc v0.4.6 includes the standalone Bridge adapter that translates this
ConsolePlugin.spec.proxy, configures service-CA trust and reaches the in-cluster Service. The adapter is not part of the production architecture.Validated result
The PoC ran through the standalone Console in oinc:
200through the Console proxy;initializereturned200and preservedMcp-Session-Id;notifications/initializedreturned202;tools/listsucceeded;tools/callreachedtoystore_greetand returnedHi Ada;connect-srcCSP report;403in a separate check.One limitation: oinc Bridge uses a configured cluster-admin token rather than a real signed-in user's
UserToken. Production acceptance still needs a real OpenShift Console test with separate viewer and Inspector users.Before production
Three areas remain:
useRole, backend review, matching UI check and persona tests.Stable error codes, response/concurrency limits, safe metrics and multi-architecture/downstream build checks are also required before release.
Acceptance criteria
UserTokenConsole proxy.status.mcpEndpoint.