Skip to Content

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 → ToolAgent.

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

FieldTypeRequiredDescription
spec.addressValueSourceyesWhere 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.transportenum (http, sse)yesTransport protocol. Defaults to http.
spec.descriptionstringnoHuman-readable description of the server.
spec.timeoutdurationnoMaximum 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.toolCallTimeoutdurationnoMaximum 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.pollIntervaldurationnoHow often the controller re-discovers tools from the server. Defaults to 1m.
spec.headers[]listnoHTTP headers added to requests to the server. Each entry has a name and a value (either a literal value or valueFromsecretKeyRef, configMapKeyRef, queryParameterRef).
spec.authorizationobjectnoOAuth 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.tokenSecretRefobjectyes (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.namestringyesName of the Secret.
spec.authorization.tokenSecretRef.accessTokenKeystringnoSecret key holding the access token. Defaults to access_token.
spec.authorization.tokenSecretRef.refreshTokenKeystringnoSecret key holding the refresh token. Defaults to refresh_token.
spec.authorization.tokenSecretRef.expiresAtKeystringnoSecret key holding the token expiry. Defaults to expires_at.
spec.authorization.tokenSecretRef.clientIDKeystringnoSecret key holding the OAuth client ID. Defaults to client_id.
spec.authorization.tokenSecretRef.clientSecretKeystringnoSecret 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: http

For a remote server, use a direct value:

spec: address: value: https://mcp.notion.com/mcp transport: http

The 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 server

The 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 yaml
status: 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 1
state valueMeaning
(empty)Server does not require OAuth.
RequiredServer 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.
AuthorizedThe controller successfully listed tools using a Bearer token resolved from spec.authorization.tokenSecretRef.
DiscoveryFailedServer 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.overrides

So 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 this

Token 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:11Z

authorized-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 tools

Status fields

FieldDescription
status.resolvedAddressThe actual address the controller resolved from spec.address.
status.toolCountNumber of tools discovered from this MCP server. Shown in the TOOLS print column.
status.authorizationOAuth 2.1 / RFC 9728 discovery metadata. Populated only when the server responds with HTTP 401; absent otherwise.
status.authorization.stateOne of Required, DiscoveryFailed, Authorized. Shown in the AUTH print column. Empty means authorization is not required.
status.authorization.resourceCanonical URI of the protected MCP resource (RFC 9728 resource).
status.authorization.resourceMetadataURLresource_metadata URL parsed from the server’s WWW-Authenticate header (RFC 9728 §5.1).
status.authorization.resourceNameHuman-readable name of the protected resource (RFC 9728 resource_name).
status.authorization.authorizationServersAuthorization server issuers the resource trusts (RFC 9728 authorization_servers).
status.authorization.scopesSupportedOAuth scopes advertised by the authorization server (RFC 8414 scopes_supported).
status.authorization.grantTypesSupportedOAuth grant types the authorization server supports (RFC 8414 grant_types_supported).
status.authorization.registrationEndpointRFC 7591 dynamic client registration endpoint, when supported.
status.authorization.authorizationEndpointOAuth 2.1 authorization endpoint (RFC 8414 authorization_endpoint).
status.authorization.tokenEndpointOAuth 2.1 token endpoint (RFC 8414 token_endpoint).
status.authorization.lastDiscoveredTimestamp of the most recent successful discovery probe.
status.authorization.expiresAtAbsolute 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.

kubectl get mcpservers prints NAME, AVAILABLE (Available condition status), DISCOVERING (Discovering condition status), TOOLS (.status.toolCount), AUTH (.status.authorization.state), and AGE.

  • Tools — the type: mcp Tool 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.
Last updated on