Skip to content

Validate the MCP Inspector backend proxy approach #776

Description

@jasonmadigan

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:

  1. reads the selected MCPGatewayExtension with the Console-supplied user token;
  2. requires Ready=True for the current extension generation;
  3. reads the Gateway referenced by spec.targetRef with the same token;
  4. finds the target listener named by sectionName;
  5. takes the host from spec.publicHost, falling back to the listener hostname;
  6. takes the scheme and port from the listener and appends /mcp;
  7. 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:

  1. Invocation authorization: add the use Role, backend review, matching UI check and persona tests.
  2. Target trust: add strict Kubernetes name checks, target-revision binding, accepted-listener provenance, dial-time address policy, dedicated transports and trusted CA mounts.
  3. 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

  • The Console Plugin image serves frontend assets and the narrow MCP relay from one Go process.
  • The Operator reconciles the backend-capable Deployment and a UserToken Console proxy.
  • The browser selects an extension by namespace/name and never supplies a destination URL.
  • The relay derives the endpoint without status.mcpEndpoint.
  • OpenShift and MCP credentials remain separate.
  • Initialize, tool refresh and a live tool call pass through the Console proxy.
  • The live browser journey has no gateway CSP dependency.
  • Remaining production hardening is recorded here and on MCP Inspector — Tools #671.

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationtriage/acceptedReviewed, prioritised, and ready for work

Type

No type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions