MCPServer
An MCPServer connects Ark to an external Model Context Protocol server, so the tools it exposes become available to agents. The controller resolves the server’s address, discovers its tools on a poll loop, and — for OAuth-protected servers — detects the authorization requirement and surfaces the discovered OAuth metadata in status. Agents don’t reference an MCPServer directly: it is wired in as MCPServer → Tool → Agent.
Spec
apiVersion: ark.mckinsey.com/v1alpha1
kind: MCPServer
metadata:
name: github-mcp
namespace: default
spec:
# --- Required ----------------------------------------------------------
address: # where the MCP server lives (ValueSource)
valueFrom:
serviceRef:
name: github-mcp-server
namespace: default
port: http
path: mcp
# --- Common optional fields --------------------------------------------
transport: http # http | sse (default 'http')
description: "GitHub repository operations via MCP protocol"
timeout: 30s # max duration to connect to the server (default '30s')
toolCallTimeout: 5m # max duration for each tool call (default: unbounded)
pollInterval: 1m # tool re-discovery interval (default '1m')
headers: # extra HTTP headers sent to the server
- name: X-Api-Key
value:
valueFrom:
secretKeyRef:
name: github-mcp-credentials
key: api-key
# --- OAuth-protected servers (RFC 9728) --------------------------------
authorization:
tokenSecretRef:
name: notion-mcp-oauth # Secret holding OAuth tokens (same namespace)Fields
| Field | Type | Required | Description |
|---|---|---|---|
spec.address | ValueSource | yes | Where the MCP server is reachable. Supports a direct value, or valueFrom with serviceRef (in-cluster service), secretKeyRef, configMapKeyRef, or queryParameterRef. Resolved value is published to status.resolvedAddress. |
spec.transport | enum (http, sse) | yes | Transport protocol. Defaults to http. |
spec.description | string | no | Human-readable description of the server. |
spec.timeout | duration | no | Maximum duration to establish a connection to this server, including the connection retry window. Defaults to 30s. This does not bound tool calls — use spec.toolCallTimeout for that. |
spec.toolCallTimeout | duration | no | Maximum duration for each individual tool call to this server (30s, 5m, 10m), including any transient error retries. Raise it for long-running operations. When unset, a tool call is bounded only by the execution budget of the query that triggered it (spec.timeout on the Query). |
spec.pollInterval | duration | no | How often the controller re-discovers tools from the server. Defaults to 1m. |
spec.headers[] | list | no | HTTP headers added to requests to the server. Each entry has a name and a value (either a literal value or valueFrom — secretKeyRef, configMapKeyRef, queryParameterRef). |
spec.authorization | object | no | OAuth configuration for servers protected per RFC 9728 . When set, the resolved access token is injected as Authorization: Bearer on both tool discovery and tool calls — see Header precedence. When unset, Ark does not inject Authorization headers. |
spec.authorization.tokenSecretRef | object | yes (within authorization) | References the Secret holding OAuth tokens and client credentials. The Secret must exist in the same namespace as the MCPServer. |
spec.authorization.tokenSecretRef.name | string | yes | Name of the Secret. |
spec.authorization.tokenSecretRef.accessTokenKey | string | no | Secret key holding the access token. Defaults to access_token. |
spec.authorization.tokenSecretRef.refreshTokenKey | string | no | Secret key holding the refresh token. Defaults to refresh_token. |
spec.authorization.tokenSecretRef.expiresAtKey | string | no | Secret key holding the token expiry. Defaults to expires_at. |
spec.authorization.tokenSecretRef.clientIDKey | string | no | Secret key holding the OAuth client ID. Defaults to client_id. |
spec.authorization.tokenSecretRef.clientSecretKey | string | no | Secret key holding the OAuth client secret. Defaults to client_secret. |
Address
spec.address is a ValueSource, so the server location can come from a literal string or be resolved at reconcile time. The most common form is a serviceRef pointing at an in-cluster MCP service:
spec:
address:
valueFrom:
serviceRef:
name: github-mcp-server
namespace: default
port: http # port name; falls back to the service's only/first port
path: mcp # path component — typically 'mcp' or 'sse'
transport: httpFor a remote server, use a direct value:
spec:
address:
value: https://mcp.notion.com/mcp
transport: httpThe resolved endpoint is written to status.resolvedAddress.
Using MCP servers with agents
Agents never reference an MCPServer directly. A Tool of type: mcp binds a specific tool exposed by the server, and the agent references that Tool:
apiVersion: ark.mckinsey.com/v1alpha1
kind: Agent
metadata:
name: github-agent
spec:
prompt: You are a GitHub assistant with access to repository operations.
modelRef:
name: gpt-4-model
tools:
- type: mcp
name: github-get-repo # References a Tool that connects to this MCP serverThe controller discovers tools from the server every spec.pollInterval and publishes the count to status.toolCount. See Tools for creating Tool resources that connect to MCP servers.
Transient error retry
Tool calls that fail with a transient error — HTTP 429, 500, 502, 503, 504, or a connection-level failure where the request never reached the server — are retried with exponential backoff and jitter. A Retry-After header from the server sets the minimum delay for the next attempt, capped at the maximum backoff. Every other failure (auth failures, validation errors, application-level tool errors, statuses that terminate the MCP session) fails immediately with the original error.
Retries are bounded by attempt count and wall-clock budget, whichever hits first. Both are configured on the completions executor chart:
env:
ARK_MCP_TOOL_CALL_MAX_ATTEMPTS: "3"
ARK_MCP_TOOL_CALL_RETRY_BUDGET_SECONDS: "30"spec.toolCallTimeout bounds the whole call — every attempt plus the backoff between them — so it composes with the retry budget: whichever expires first ends the call.
Retry activity is counted in the ark_mcp_tool_call_retries_total{result, server} metric on the completions executor’s /metrics endpoint: transient_error for each scheduled retry, then one terminal success, permanent_error, or exhausted per call that needed retries.
OAuth-protected servers
Remote MCP servers that require OAuth (e.g. https://mcp.notion.com/mcp, GitHub Copilot MCP) respond to unauthenticated requests with HTTP 401 and a WWW-Authenticate: Bearer challenge per RFC 9728 and the MCP 2025-06-18 authorization spec .
When Ark detects this, it performs OAuth metadata discovery and populates status.authorization:
kubectl get mcpserver notion -o yamlstatus:
authorization:
state: Required # Required | DiscoveryFailed | Authorized
resource: https://mcp.notion.com/mcp
resourceName: Notion MCP (Beta)
authorizationServers: [https://mcp.notion.com]
authorizationEndpoint: https://mcp.notion.com/authorize
tokenEndpoint: https://mcp.notion.com/token
registrationEndpoint: https://mcp.notion.com/register
grantTypesSupported: [authorization_code, refresh_token]
conditions:
- type: Available
status: "False"
reason: AuthorizationRequired
message: "OAuth authorization required for Notion MCP (Beta)..."kubectl get mcpservers shows the state in the AUTH column:
NAME AVAILABLE TOOLS AUTH
notion False Required
github False Required
shell True 1state value | Meaning |
|---|---|
| (empty) | Server does not require OAuth. |
Required | Server returned 401 and metadata discovery succeeded. Ready for an authorize flow. A 401 from an authorized server (expiry, revocation, refresh failure) also collapses back to Required and emits a TokenRejected event. |
Authorized | The controller successfully listed tools using a Bearer token resolved from spec.authorization.tokenSecretRef. |
DiscoveryFailed | Server returned 401 but no usable RFC 9728 metadata was found. The CLI cannot drive an OAuth flow against this server. |
Header precedence
The access token from spec.authorization.tokenSecretRef authorizes both paths: the controller’s tool discovery, and the tool calls an agent makes at query time. status.authorization.state: Authorized therefore means agents can call the server’s tools, not just list them.
Headers merge with this precedence (last wins):
spec.headers < spec.authorization < Agent.spec.overrides < Query.spec.overridesSo the resolved bearer replaces an Authorization entry in spec.headers, and an Agent or Query override replaces the bearer for that run. See User authorised MCP servers for the per-user pattern.
Both the Secret named by tokenSecretRef and any Secret or ConfigMap referenced from spec.headers are read from the MCPServer’s namespace, not the caller’s.
Prerequisites
Before running ark mcp auth login, spec.authorization.tokenSecretRef.name must be set on the MCPServer. ark-api returns 422 with an actionable message if the field is missing:
spec:
authorization:
tokenSecretRef:
name: notion-mcp-oauth # operator must declare thisToken writes go through ark-api
The interactive authorize flow (dynamic client registration, PKCE, token exchange, Secret write) is orchestrated by ark-api. The ark mcp auth login CLI is a thin client that POSTs to ark-api’s /api/v1/mcp-servers/{name}/auth/start and polls auth/status — it never touches the Kubernetes Secret directly. The CLI is the only client today; a dashboard authorize flow is not yet available. See MCP OAuth Callback for the operator-side callback URL configuration.
A successful flow leaves two annotations on the MCPServer:
metadata:
annotations:
ark.mckinsey.com/mcp-auth-authorized-by: cli # cli (dashboard not yet supported)
ark.mckinsey.com/mcp-auth-authorized-at: 2026-05-19T14:32:11Zauthorized-by is cli for this phase. A future per-user-tokens capability will surface the OIDC subject of the authenticated user here so multi-user clusters can distinguish whose credentials wrote the Secret. Until that ships, MCPServer tokens are effectively a per-server singleton — only one user can hold an authorized session at a time.
Status
status:
resolvedAddress: http://github-mcp-server.default.svc:8080/mcp
toolCount: 12
authorization:
state: Authorized
resource: https://mcp.notion.com/mcp
resourceName: Notion MCP (Beta)
resourceMetadataURL: https://mcp.notion.com/.well-known/oauth-protected-resource
authorizationServers: [https://mcp.notion.com]
scopesSupported: [read, write]
grantTypesSupported: [authorization_code, refresh_token]
registrationEndpoint: https://mcp.notion.com/register
authorizationEndpoint: https://mcp.notion.com/authorize
tokenEndpoint: https://mcp.notion.com/token
lastDiscovered: 2026-05-19T14:30:00Z
expiresAt: 2026-05-19T15:32:11Z
conditions:
- type: Available
status: "True"
reason: ToolsDiscovered
message: Discovered 12 toolsStatus fields
| Field | Description |
|---|---|
status.resolvedAddress | The actual address the controller resolved from spec.address. |
status.toolCount | Number of tools discovered from this MCP server. Shown in the TOOLS print column. |
status.authorization | OAuth 2.1 / RFC 9728 discovery metadata. Populated only when the server responds with HTTP 401; absent otherwise. |
status.authorization.state | One of Required, DiscoveryFailed, Authorized. Shown in the AUTH print column. Empty means authorization is not required. |
status.authorization.resource | Canonical URI of the protected MCP resource (RFC 9728 resource). |
status.authorization.resourceMetadataURL | resource_metadata URL parsed from the server’s WWW-Authenticate header (RFC 9728 §5.1). |
status.authorization.resourceName | Human-readable name of the protected resource (RFC 9728 resource_name). |
status.authorization.authorizationServers | Authorization server issuers the resource trusts (RFC 9728 authorization_servers). |
status.authorization.scopesSupported | OAuth scopes advertised by the authorization server (RFC 8414 scopes_supported). |
status.authorization.grantTypesSupported | OAuth grant types the authorization server supports (RFC 8414 grant_types_supported). |
status.authorization.registrationEndpoint | RFC 7591 dynamic client registration endpoint, when supported. |
status.authorization.authorizationEndpoint | OAuth 2.1 authorization endpoint (RFC 8414 authorization_endpoint). |
status.authorization.tokenEndpoint | OAuth 2.1 token endpoint (RFC 8414 token_endpoint). |
status.authorization.lastDiscovered | Timestamp of the most recent successful discovery probe. |
status.authorization.expiresAt | Absolute expiry time of the current access token, published for consumers that can get mcpservers but not secrets. |
status.conditions[] | Standard Kubernetes conditions. Available reflects readiness; Discovering reflects tool-discovery progress. |
Print columns
kubectl get mcpservers prints NAME, AVAILABLE (Available condition status), DISCOVERING (Discovering condition status), TOOLS (.status.toolCount), AUTH (.status.authorization.state), and AGE.
Related
- Tools — the
type: mcpTool that binds a server’s tool to an agent. - Agents — reference MCP tools via
spec.tools. - Query — runs work against agents that use MCP tools.
- MCP OAuth Callback — operator-side callback URL configuration for the authorize flow.