Last generated September 30, 2026
MCPServer
MCPServer is the Schema for the mcpservers API
Version v1alpha1
Example CR
apiVersion: muster.giantswarm.io/v1alpha1
kind: MCPServer
metadata:
name: git-tools
namespace: default
spec:
type: stdio
autoStart: true
command: npx
args: ["@modelcontextprotocol/server-git"]
env:
GIT_ROOT: "/workspace"
LOG_LEVEL: "info"
description: "Git tools MCP server for repository operations"
Properties
.apiVersion
APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
.kind
Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
.metadata
.spec
MCPServerSpec defines the desired state of MCPServer
.spec.args
Args specifies the command line arguments for stdio type servers. This field is only available when Type is “stdio”.
.spec.args[*]
.spec.auth
Auth configures authentication behavior for this MCP server. This is only relevant for remote servers (streamable-http or sse).
.spec.auth.authorizationServer
AuthorizationServer is an opt-out for backends that don’t publish RFC 9728 Protected Resource Metadata. When set, muster’s per-server OAuth login flow (core_auth_login) skips PRM probing and uses these values directly. muster logs each override use at INFO so non-compliance is observable.
This override does NOT bypass mcp-go’s connect-time PRM probe; backends
without RFC 9728 metadata still reconcile to “Auth Required” on first
connect, then transition to “Connected” after muster auth login.
Setting AuthorizationServer does NOT change the RFC 8707 resource
parameter — that remains driven by the MCP server URL.
AuthorizationServer is mutually exclusive with ForwardToken: true and TokenExchange.Enabled: true. The CRD admission rules above reject any CR that combines them. Only valid when Type is “oauth”. A server that needs the session’s ID token next to the pinned grant sets ForwardIdentity instead.
Use case: Atlassian Remote MCP and similar backends that publish RFC 8414 metadata at their resource origin instead of via RFC 9728.
.spec.auth.authorizationServer.authorizationEndpoint
AuthorizationEndpoint is the authorization server’s authorization endpoint. Set it together with TokenEndpoint for an authorization server that publishes no RFC 8414 / OIDC discovery document (GitHub: https://github.com/login/oauth/authorize). muster then performs no discovery for this issuer and assumes S256 PKCE support, which the operator asserts by pinning the endpoints.
.spec.auth.authorizationServer.clientCredentialsSecretRef
ClientCredentialsSecretRef references a Kubernetes Secret holding a
client registered with this authorization server out of band (a GitHub
App or OAuth App). When set, muster identifies itself with these
credentials instead of its Client ID Metadata Document or a dynamic
registration – the only option against an authorization server that
supports neither. The secret carries client-id and client-secret
(key names configurable in the reference).
.spec.auth.authorizationServer.clientCredentialsSecretRef.clientIdKey
ClientIDKey is the key in the secret data that contains the client ID. Defaults to “client-id” if not specified.
.spec.auth.authorizationServer.clientCredentialsSecretRef.clientSecretKey
ClientSecretKey is the key in the secret data that contains the client secret. Defaults to “client-secret” if not specified.
.spec.auth.authorizationServer.clientCredentialsSecretRef.name
Name is the name of the Kubernetes Secret. Required.
.spec.auth.authorizationServer.clientCredentialsSecretRef.namespace
Namespace is the Kubernetes namespace where the secret is located. If not specified, defaults to the MCPServer’s namespace.
.spec.auth.authorizationServer.expectedIssuer
ExpectedIssuer is the issuer identifier the authorization server itself
publishes and puts in the RFC 9207 iss parameter of its authorization
responses, when that differs from Issuer. Issuer stays the identity the
grants are filed under; ExpectedIssuer only decides which value a
present iss on the callback has to equal. Set it when several
MCPServers pin one authorization server under different identities so
their grants stay apart – two GitHub Apps, say: each is pinned under
its own identity (https://github.com/apps/
.spec.auth.authorizationServer.grantScope
GrantScope decides whom a token obtained from this authorization server
belongs to. session (the default) keeps today’s behavior: the token is
bound to the login session that completed the browser flow, so every new
session signs in again. subject files the token under the user’s
identity as well, so any later session of the same person – another
client, a re-login, a front-end whose bearer rotates – reuses the grant
without a new consent, until the person signs out of this server
(core_auth_logout) or everywhere. Use subject for external accounts
the person owns (GitHub), never for tokens that carry session-specific
authority.
.spec.auth.authorizationServer.issuer
Issuer is the OAuth 2.0 / OIDC issuer URL of the authorization server the sign-in runs against, and the identity the session’s grants are filed under. It takes precedence over the authorization server the endpoint’s own RFC 9728 metadata names: the browser flow, the token store, the connection and core_auth_logout all use this issuer, so an endpoint that accepts tokens from an authorization server other than the one it advertises is pinned by naming the accepting one here. It may be muster’s own identity provider; muster keeps the session’s login token apart from the grant. A change to this field takes effect on the next core_auth_login, without a restart. muster fetches AS metadata via the existing OAuth client, which performs RFC 8414 / OIDC discovery against this issuer – unless AuthorizationEndpoint and TokenEndpoint are set, in which case no discovery happens and the issuer is only that identity. When the authorization server publishes a different issuer identifier than the identity pinned here, set ExpectedIssuer.
.spec.auth.authorizationServer.scopes
Scopes is the OAuth scope parameter value (RFC 6749 §3.3 wire format: space-separated scope tokens). Matches existing TokenExchangeConfig.Scopes.
.spec.auth.authorizationServer.tokenEndpoint
TokenEndpoint is the authorization server’s token endpoint (GitHub: https://github.com/login/oauth/access_token). See AuthorizationEndpoint.
.spec.auth.forwardIdentity
ForwardIdentity sends the session’s upstream ID token – the token ForwardToken would put in Authorization – in the X-Muster-Id-Token header on every request to this server, next to the grant of its pinned authorization server, which Authorization keeps carrying unchanged. It is for a server that acts for the person on two systems: one through the pinned grant (a GitHub App user token, say) and one through calls back with the person’s own identity (their Kubernetes access). The token is read on every request, so a refreshed one is sent once the session holds it; a session without an upstream ID token sends no header, and a server without this field never receives it.
Only valid with Type “oauth” and AuthorizationServer set. RequiredAudiences of such a server are requested at login as for a ForwardToken server, so the forwarded ID token carries them.
The forwarded ID token is not audience-scoped to this server, as with ForwardToken: the same token is accepted by every backend that trusts its issuer and audiences, so a ForwardIdentity server must be trusted as much as a ForwardToken backend.
.spec.auth.forwardToken
ForwardToken enables ID token forwarding for SSO. When true, muster forwards the session’s upstream dex ID token (or, for sessions established by a trusted-issuer bearer, that IdP-issued bearer) to this server byte-identical, instead of triggering a separate OAuth flow. Every forwarded token is issued by the IdP (dex); muster is not an identity provider, never signs tokens, and no backend is ever configured to trust a muster JWKS. The downstream server must trust the IdP’s issuer/JWKS (e.g. muster’s client ID in its TrustedAudiences for forwarded dex ID tokens).
The forwarded token is not audience-scoped to this server: the same token is accepted by every forwardToken backend, so all forwardToken backends must be equally trusted. A token’s nested act claim (minted by the IdP, e.g. via exchange at dex) carries the delegation chain for backend authorization decisions.
.spec.auth.requiredAudiences
RequiredAudiences specifies additional audience(s) that the forwarded ID token should contain. When ForwardToken is true, muster will request these audiences from the upstream IdP (e.g., Dex) using cross-client scopes.
This is used when the downstream server requires tokens with specific audiences, for example when forwarding tokens to Kubernetes for OIDC authentication: requiredAudiences: - “dex-k8s-authenticator”
At user authentication, muster collects all requiredAudiences from MCPServers with forwardToken: true or forwardIdentity: true and requests them all from the IdP.
.spec.auth.requiredAudiences[*]
.spec.auth.sigv4
SigV4 configures AWS Signature Version 4 request signing. Required when Type is “sigv4”, and rejected otherwise.
Unlike the OAuth mechanisms above, this is a machine identity: every request signs as muster itself, not as the calling user, so it never touches the session-scoped client machinery. Only valid together with spec.type “streamable-http”.
ForwardToken and TokenExchange are rejected alongside it by the rule above. AuthorizationServer needs no rule of its own: it already requires Type “oauth”, which the sigv4 pairing rule excludes.
.spec.auth.sigv4.region
Region is the SigV4 signing region. It must match the region in URL, because the signature’s credential scope is checked against the endpoint. This is not the region the backend operates in — that is MCPServerSpec.Meta.
.spec.auth.sigv4.roleArn
RoleARN, when set, is assumed from the pod’s base credentials before signing. Leave empty to sign as the pod’s own identity.
.spec.auth.sigv4.service
Service is the SigV4 signing service name. It defaults to the first hostname label of URL, which is how AWS’s own clients derive it: aws-mcp.eu-central-1.api.aws signs as service “aws-mcp”.
.spec.auth.tokenExchange
TokenExchange enables SSO via RFC 8693 Token Exchange for cross-cluster SSO. When configured, muster exchanges its local token for a token valid on the remote cluster’s Identity Provider (e.g., Dex).
Use TokenExchange when: - The remote cluster has its own Dex instance - The remote Dex is configured with an OIDC connector for muster’s Dex - You need a token issued by the remote cluster’s IdP (not just forwarded)
Token exchange takes precedence over ForwardToken if both are configured.
.spec.auth.tokenExchange.clientCredentialsSecretRef
ClientCredentialsSecretRef references a Kubernetes Secret containing client credentials for authenticating with the remote Dex’s token endpoint. This is required when the remote Dex requires client authentication for token exchange (RFC 8693).
The secret should contain: - client-id: The OAuth client ID registered on the remote Dex - client-secret: The OAuth client secret for authentication
Example secret:
apiVersion: v1
kind: Secret
metadata:
name: grizzly-token-exchange-credentials
namespace: muster
type: Opaque
stringData:
client-id: muster-token-exchange
client-secret: <secret-value>
.spec.auth.tokenExchange.clientCredentialsSecretRef.clientIdKey
ClientIDKey is the key in the secret data that contains the client ID. Defaults to “client-id” if not specified.
.spec.auth.tokenExchange.clientCredentialsSecretRef.clientSecretKey
ClientSecretKey is the key in the secret data that contains the client secret. Defaults to “client-secret” if not specified.
.spec.auth.tokenExchange.clientCredentialsSecretRef.name
Name is the name of the Kubernetes Secret. Required.
.spec.auth.tokenExchange.clientCredentialsSecretRef.namespace
Namespace is the Kubernetes namespace where the secret is located. If not specified, defaults to the MCPServer’s namespace.
.spec.auth.tokenExchange.connectorId
ConnectorID is the ID of the OIDC connector on the remote Dex that trusts the local cluster’s Dex. Required when Enabled is true. Example: “cluster-a-dex”
.spec.auth.tokenExchange.dexTokenEndpoint
DexTokenEndpoint is the URL used to connect to the remote cluster’s Dex token endpoint. This may differ from the issuer URL when access goes through a proxy. Required when Enabled is true. Example: https://dex.cluster-b.example.com/token (direct) Example: https://dex-cluster.proxy.example.com/token (via proxy)
.spec.auth.tokenExchange.enabled
Enabled determines whether token exchange should be attempted.
.spec.auth.tokenExchange.expectedIssuer
ExpectedIssuer is the expected issuer URL in the exchanged token’s “iss” claim. This should match the remote Dex’s configured issuer URL. When access goes through a proxy, this differs from DexTokenEndpoint. If not specified, the issuer is derived from DexTokenEndpoint (backward compatible). Example: https://dex.cluster-b.example.com
.spec.auth.tokenExchange.scopes
Scopes are the scopes to request for the exchanged token.
.spec.auth.type
Type specifies the authentication type. Supported values: - “oauth”: OAuth 2.0/OIDC authentication - “none”: No authentication - “sigv4”: AWS Signature Version 4, signed with muster’s own machine identity. See SigV4.
.spec.autoStart
AutoStart determines whether this MCP server should be automatically started when the muster system initializes or when dependencies become available.
.spec.command
Command specifies the executable path for stdio type servers. This field is required when Type is “stdio”.
.spec.description
Description provides a human-readable description of this MCP server’s purpose.
.spec.env
Env contains environment variables to set for the MCP server. For stdio servers, these are passed to the process when it is started. For remote servers, these can be used for authentication or configuration.
.spec.family
Family declares that this MCP server is an instance of a family of equivalent servers (for example, multiple kubernetes MCP servers pointed at different clusters). When set, the aggregator exposes tools from all servers in the same family under a single name ({musterPrefix}{family.name}{toolName}) with a required parameter (named by family.instanceArg) that selects which instance handles the call. The parameter is always required even for single-instance families so skills written against the family name remain stable as instances are added or removed. When unset, the legacy per-server prefixing applies ({musterPrefix}{toolPrefix-or-name}{toolName}).
.spec.family.instanceArg
InstanceArg names the required parameter callers use to select which family member handles the tool call (for example “management_cluster”, “country”, “model”). All servers declaring the same family.name must agree on InstanceArg; if they diverge, the aggregator falls back to per-server prefixing for the entire family and logs a warning.
.spec.family.name
Name is the family identifier. Servers sharing the same Name expose their tools as {musterPrefix}{Name}{toolName}.
.spec.headers
Headers contains HTTP headers to send with requests to remote MCP servers. This field is only relevant when Type is “streamable-http” or “sse”.
.spec.meta
Meta contains entries merged into the params._meta object of every
outbound JSON-RPC request that carries params. It carries call-scoped
configuration to a backend that reads it from the MCP metadata field
instead of from tool arguments.
An entry already present in a request’s _meta wins, so a caller can
still override per call. Requests without params are left untouched.
The injection happens in an HTTP round tripper shared by every remote
client, so it applies to streamable-http and sse alike, with or
without auth. For a SigV4 server the round tripper sits in front of the
signer, because the body has to be rewritten before it is signed.
A stdio server has no HTTP transport, so the CEL rule above rejects the field there rather than accepting and dropping it.
The AWS-hosted MCP server is the motivating case: it takes the region it
operates in from params._meta.AWS_REGION, and its tools declare no
region argument, so without this a call fails with NoRegionError. That
operating region is a different value from Auth.SigV4.Region, which is
the signing region — the two default to the same string but resolve
independently, endpoint-first for signing and config-first for operating.
.spec.restartRequestedAt
RestartRequestedAt requests a one-shot restart of this server’s service. Restart is an action, not a steady state, so it cannot be a boolean: the caller writes a timestamp, which doubles as the audit record of when the restart was requested. The reconciler restarts the service only when this differs from status.lastRestartedAt, then mirrors the processed value into status — repeated reconciles of an already-processed request are no-ops (issue #1055).
.spec.suspended
Suspended declares the desired lifecycle state of this server’s service. When true, the reconciler stops the service (and refuses to start it) until the field is set back to false. This is the CR-driven replacement for the imperative start/stop tools: writing it is authorized and audited by the apiserver like any other spec mutation (issue #1055).
.spec.timeout
Timeout specifies the connection timeout for remote operations (in seconds)
.spec.toolPrefix
ToolPrefix is an optional prefix that will be prepended to all tool names provided by this MCP server. This helps avoid naming conflicts when multiple servers provide tools with similar names.
.spec.type
Type specifies how this MCP server should be executed. Supported values: “stdio” for local processes, “streamable-http” for HTTP-based servers, “sse” for Server-Sent Events.
“stdio” is CLI-only. A stdio server is started as a subprocess of the muster process, so a muster running in Kubernetes mode rejects it: the tool handlers fail the call, and a CR applied directly through the API server is reconciled to state Failed instead of spawning a process in the muster pod. Run the MCP server as its own workload and use “streamable-http” or “sse” instead.
.spec.url
URL is the endpoint where the remote MCP server can be reached This field is required when Type is “streamable-http” or “sse”. Examples: http://mcp-server:8080/mcp, https://api.example.com/mcp
.status
MCPServerStatus defines the observed state of MCPServer.
This status reflects server-side observable state including auth requirements. It captures infrastructure connectivity as well as whether the server demands authentication (e.g. “Auth Required”). Per-user session state (which specific user is authenticated, token expiry, etc.) is tracked separately in the Session Registry (internal/aggregator/session_registry.go).
Server-Side State (CRD): - State: Running/Connected/Starting/Connecting/Stopped/Disconnected/Auth Required/Awaiting Session/Failed - Conditions: Standard K8s conditions for detailed status
Per-User Session State (Session Registry): - ConnectionStatus: Connected, PendingAuth, Failed (per-user) - AuthStatus: Authenticated, AuthRequired, TokenExpired (per-user) - AvailableTools: Tools visible to this specific user
.status.conditions
Conditions represent the latest available observations of the MCPServer’s current state. Standard condition types: - Ready: True while the server is reachable (Running, Connected, Auth Required, Awaiting Session), False otherwise. The reason is the state in one word (Connected, AwaitingSession, AuthRequired, Connecting, Disconnected, Suspended, Failed) or, for a server Failed by its token exchange, the class of the failure (TokenExchangeCredentials, TokenExchangeEndpoint, TokenExchangeConnector). The message explains the state: for Awaiting Session the per-session mechanism, the token endpoint and connector, and when the last exchange succeeded; for Failed the error.
.status.conditions[*]
Condition contains details for one aspect of the current state of this API Resource.
.status.conditions[*].lastTransitionTime
lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
.status.conditions[*].message
message is a human readable message indicating details about the transition. This may be an empty string.
.status.conditions[*].observedGeneration
observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
.status.conditions[*].reason
reason contains a programmatic identifier indicating the reason for the condition’s last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
.status.conditions[*].status
status of the condition, one of True, False, Unknown.
.status.conditions[*].type
type of condition in CamelCase or in foo.example.com/CamelCase.
.status.consecutiveFailures
ConsecutiveFailures tracks the number of consecutive connection failures. This is used for exponential backoff and to identify unreachable servers. Reset to 0 when a connection succeeds.
.status.lastAttempt
LastAttempt indicates when the last connection attempt was made. Used with ConsecutiveFailures to implement exponential backoff.
.status.lastConnected
LastConnected indicates when the server was last successfully connected
.status.lastError
LastError contains any error message from the most recent server operation. Note: Per-user authentication errors are tracked in the Session Registry, not here. This field only contains infrastructure-level errors.
.status.lastFailureHTTPStatus
LastFailureHTTPStatus is the HTTP status code the endpoint answered the most recent failed connection attempt with, for example 504 from a gateway in front of the server or 404 from a backend whose route is not served yet. Absent when the attempt got no HTTP response at all (connection refused, DNS failure, timeout) or the last attempt succeeded. Together with LastError it tells an upstream outage from an endpoint nothing listens on.
.status.lastRestartedAt
LastRestartedAt mirrors the spec.restartRequestedAt value most recently processed by the reconciler. A restart is performed only when the two differ (spec-is-desired / status-is-observed idiom, issue #1055).
.status.nextRetryAfter
NextRetryAfter indicates the earliest time when the next retry should be attempted. The wait doubles with every consecutive failure, starting at 30 seconds, and is capped (2 minutes by default) so a recovered upstream is noticed within the cap plus the retry tick, however long the outage lasted. Absent while no retry is scheduled.
.status.restartCount
RestartCount tracks how many times this server has been restarted (stdio only)
.status.state
State represents the high-level infrastructure state of the MCP server. This is independent of user session state (authentication, connection status).
For stdio servers: Running, Starting, Stopped, Failed For remote servers: Connected, Auth Required, Awaiting Session, Connecting, Disconnected, Failed
This documentation page shows information based on muster v5.32.10.
Need help with the platform API?
We listen in your Slack support channel. And of course, we welcome your pull requests to improve these docs!