Publish an MCP Server

View as Markdown

Use this guide to make a Model Context Protocol (MCP) server available through DSX Agent Gateway. You can discover a Kubernetes Service by labels or configure a server with a static address.

Understand the Request Path

Agent Gateway authenticates the caller, derives a tenant ID, and checks access to the selected MCP target. The gateway then forwards the original bearer token to the selected server.

Your MCP server remains responsible for authorizing the caller within its domain. Do not use caller-supplied tenant headers as an authenticated identity.

Use the architecture guide for the complete direct and bridged request paths.

Prepare the MCP Server

Your server must implement MCP over Streamable HTTP at /mcp and advertise only capabilities that it supports.

Static upstreams can use the legacy Server-Sent Events (SSE) transport when required. Use Streamable HTTP for new servers.

Choose an Upstream Type

Choose the upstream type that matches how the gateway reaches the MCP server:

Upstream TypeUse WhenRuntime Target NameRequired Configuration
SelectorThe gateway can discover a Kubernetes Service by namespace and labels.<service-name>-<port-name>A Service namespace, matching labels, and a named port with appProtocol: agentgateway.dev/mcp.
StaticThe server has a stable HTTP or HTTPS address.The upstreams map key.A complete address and an optional transport protocol.

Use the selector workflow for a Kubernetes Service. Use the static workflow when Service discovery cannot represent the target.

Publish a Kubernetes Service

Create a named Service port for the MCP endpoint. Set the port’s appProtocol to agentgateway.dev/mcp.

apiVersion: v1
kind: Service
metadata:
name: inventory
namespace: agent-services
labels:
app.kubernetes.io/name: inventory-mcp
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: inventory-mcp
ports:
- name: mcp
port: 3001
targetPort: mcp
appProtocol: agentgateway.dev/mcp

The Service selector must identify the Pods that serve /mcp on the named target port. Expose a separate health endpoint for Kubernetes startup, readiness, and liveness probes, and configure the probes before you publish the workload. The health endpoint is not an MCP method and does not replace /mcp.

Configure Service Discovery

Add a selector entry under upstreams in the Agent Gateway values. The serviceLabels map matches labels on the Service, not labels only on its Pods.

upstreams:
inventory-selector:
namespace: agent-services
serviceLabels:
app.kubernetes.io/name: inventory-mcp

The upstream entry name identifies the selector configuration. The discovered MCP target name is <service-name>-<port-name>. The example Service is named inventory and its port is named mcp, so it creates the target inventory-mcp. Service labels select the Service, but they do not contribute to the target name.

The bundled Agent Gateway controller watches all namespaces by default. If the deployment restricts discovery with agentgateway.discoveryNamespaceSelectors, include the MCP server namespace.

The gateway skips an unavailable target when it builds a combined catalog, so healthy targets remain available. Use the operations guide to monitor discovery and programming status.

Configure a Static Upstream

Use a static upstream when the MCP server has a stable HTTP or HTTPS address. The map key becomes the target name.

upstreams:
inventory-external:
mode: static
address: https://inventory.example.com/mcp
protocol: StreamableHTTP
requestTimeout: 10s

The address must include the scheme, host, and path. The chart accepts http and https schemes. An HTTPS address enables Transport Layer Security from the gateway to the server.

Do not include credentials, a query string, or a fragment in the static address. Use protocol: SSE only for a server that requires the legacy SSE transport.

Configure Timeouts

upstreamRequestTimeout sets the default deadline for receiving response headers from every MCP target. The default is 5s. Streaming response bodies can continue after this deadline.

Set upstreams.<name>.requestTimeout when one target needs a different response-header deadline.

upstreamRequestTimeout: 5s
upstreams:
inventory-external:
mode: static
address: https://inventory.example.com/mcp
requestTimeout: 10s

Set a timeout that covers the server’s normal time to produce response headers. Do not use the response-header timeout as the maximum duration for a streaming tool call.

Grant Tenant Access

The configured operator tenant can access every MCP target. Every other authenticated tenant can access only targets in auth.cel.unprivilegedTenantMCPs.

Add the runtime target name, not the selector configuration name.

auth:
cel:
operatorTenantId: oidc:operator
unprivilegedTenantMCPs:
- inventory-mcp
- inventory-external

In this example, inventory-mcp is the discovered Service target. inventory-external is the static target.

The current allowlist applies to all authenticated non-operator tenants. Apply finer operation-level authorization in the MCP server after it validates the forwarded token.

Understand Published Names

When the gateway aggregates multiple targets, it prefixes tools and prompts with their target name. The published name has the form <target>_<name>.

For example, the lookup tool from inventory-mcp is published as inventory-mcp_lookup. Clients must use the complete published name in tools/call. An unprefixed tool or prompt name does not select a default target.

Choose stable, lowercase, hyphenated Service, port, and static target names. Changing a target name also changes its published tool and prompt names.

Apply the Configuration

For a selector upstream, apply the MCP server workload and Service before you update the gateway release.

kubectl apply --filename inventory-mcp.yaml

Apply your updated values by following the deployment guide.

Verify the Published Server

Confirm that the Service exposes an MCP port with the required application protocol.

export MCP_NAMESPACE='agent-services'
export MCP_SERVICE='inventory'
kubectl get service "${MCP_SERVICE}" \
--namespace "${MCP_NAMESPACE}" \
--output jsonpath='{range .spec.ports[*]}{.name}{"\t"}{.appProtocol}{"\n"}{end}'

The MCP port must show agentgateway.dev/mcp.

Confirm that the gateway is programmed after the release update.

kubectl wait \
--namespace dsx-agent-gateway \
--for=condition=Programmed \
gateway/dsx-agent-gateway \
--timeout=5m

Follow the client quickstart to initialize a session and call tools/list. Confirm that an authorized tenant sees the expected prefixed tool names. Confirm that a tenant without target access does not see or invoke the target.

If the target is absent, check the following configuration.

  • The Service labels match upstreams.<name>.serviceLabels.
  • The selector uses the Service namespace.
  • The Service port is named and sets appProtocol: agentgateway.dev/mcp.
  • The controller watches the server namespace.
  • auth.cel.unprivilegedTenantMCPs contains the runtime target name for non-operator access.
  • The MCP server responds at /mcp before the response-header deadline.

Use the configuration reference for every chart-owned setting and default.