Technical Reference
MCP protocol
-
Specification: https://modelcontextprotocol.io/specification
-
Python SDK: https://github.com/modelcontextprotocol/python-sdk (
mcp[cli]v2.1+) -
Transport: Streamable HTTP (HTTP POST + SSE responses) — the production transport for remote servers. Stdio transport is used for local development.
Key protocol methods
| Method | Description |
|---|---|
|
Client → Server handshake, exchanges capabilities |
|
Client discovers available tools (names, descriptions, parameters) |
|
Client invokes a tool by name with JSON arguments |
|
Client discovers available data resources (optional) |
|
Client discovers prompt templates (optional) |
MCPServer CR reference
apiVersion: mcp.x-k8s.io/v1alpha1
kind: MCPServer
metadata:
name: <user-prefix>-stock-mcp
spec:
source:
type: ContainerImage
containerImage:
ref: <image-registry-url>@sha256:<digest>
config:
port: 8080
path: /mcp
storage:
- path: /tmp
permissions: ReadWrite
source:
type: EmptyDir
emptyDir: {}
- path: /.cache
permissions: ReadWrite
source:
type: EmptyDir
emptyDir: {}
mcp:
stateless: true
runtime:
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"
The config.storage entries are required. The operator enforces a read-only root filesystem, so yfinance (writes to /.cache) and Python (writes to /tmp) need writable EmptyDir mounts. Without them, the pod crashes with Read-only file system.
|
Status conditions
| Condition | Meaning |
|---|---|
|
Deployment running, handshake successful, service reachable |
|
Pods not ready — check events and pod logs |
|
Server did not respond to MCP |
What the operator creates
When you apply an MCPServer CR, the MCP Lifecycle Operator creates:
-
Deployment — security-hardened (non-root UID, drop ALL capabilities, read-only root filesystem, seccomp RuntimeDefault)
-
Service — ClusterIP targeting the configured port
-
NetworkPolicy — restricts ingress to the service port
All owned resources are garbage-collected when the MCPServer CR is deleted.
MCP Gateway (RHCL) reference
HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: <user-prefix>-stock-mcp
spec:
parentRefs:
- name: mcp-gateway
namespace: mcp-system
kind: Gateway
hostnames:
- <gateway-hostname>
rules:
- matches:
- path:
type: PathPrefix
value: /mcp/<user-prefix>-stock
backendRefs:
- name: <user-prefix>-stock-mcp
port: 8080
MCPServerRegistration
apiVersion: mcp.kuadrant.io/v1alpha1
kind: MCPServerRegistration
metadata:
name: <user-prefix>-stock-mcp
spec:
prefix: <user-prefix>_stock # <-- field is 'prefix', NOT 'toolPrefix'
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: <user-prefix>-stock-mcp
The field is prefix, not toolPrefix. Using toolPrefix is silently pruned and has no effect.
|
The targetRef references the HTTPRoute (not the MCPServer directly). The gateway controller discovers the backend service from the route.
Python SDK v2 patterns
Server setup
from mcp.server import MCPServer
server = MCPServer(
"stock-market",
instructions="Provides comprehensive financial data for stocks.",
)
Tool definition
@server.tool(
name="get_stock_info",
description="Get comprehensive stock information including price, metrics, and company details.",
)
async def get_stock_info(ticker: str) -> str:
"""Get stock information for a given ticker symbol.
Args:
ticker: Stock ticker symbol (e.g., AAPL, MSFT, GOOGL)
"""
try:
import yfinance as yf
company = yf.Ticker(ticker)
info = company.info
if not info or "symbol" not in info:
return f"No stock info found for ticker {ticker}."
return json.dumps(info)
except Exception as e:
return f"Error: getting stock information for {ticker}: {e}"
Key patterns:
-
@server.tool(name=…, description=…)— explicit name and description for the LLM -
Type hints on all parameters —
str→"type": "string",int→"type": "integer",float | None→ optional number -
Parameters without defaults become
requiredin the JSON Schema -
async def— enables concurrent request handling via asyncio -
Return type is always
str— tool results go intoresult.content[0].textin the MCP response -
Wrap logic in try/except — return error strings, never raise exceptions from tools
Running the server (dual transport)
import os
def main() -> None:
transport = os.environ.get("MCP_TRANSPORT", "stdio")
if transport == "streamable-http":
server.run(transport="streamable-http", host="0.0.0.0", port=8080)
else:
server.run(transport="stdio")
if __name__ == "__main__":
main()
For container deployments, host must be "0.0.0.0" (not the default "127.0.0.1"), otherwise the server is unreachable from outside the pod. Set MCP_TRANSPORT=streamable-http in the Dockerfile via ENV.
|
Useful oc commands
# Check MCPServer status
oc get mcpserver
# Describe for events and conditions
oc describe mcpserver <name>
# Check build logs
oc logs bc/<name>
# Check server pod logs
oc logs deployment/<name>
# Check MCP Gateway registration
oc get mcpserverregistration
# Check HTTPRoute attachment
oc describe httproute <name>
# Quick MCP health check (from within the cluster)
curl -s http://<service-name>:8080/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'