Guide · Kubernetes & Infrastructure as Code
MCP Tools for Crossplane — Provider health, ManagedResource status, and CompositeResource reconciliation
Crossplane extends Kubernetes to provision and manage cloud infrastructure using the same declarative API patterns as Kubernetes itself. When building MCP tools that monitor Crossplane, three distinctions determine correctness: Provider installation health vs ManagedResource reconciliation health (a Provider with Healthy: True means the provider pod is running, not that any specific ManagedResource is reconciling correctly), the Synced condition vs the Ready condition (Synced: True means the provider successfully sent the desired state to the cloud API; Ready: True means the cloud resource is in the desired state and operational — these can diverge during long-provisioning operations like RDS instance creation), and dependency chains in CompositeResources (a CompositeResource's Ready: False often cascades from a leaf ManagedResource that failed — tracing the dependency chain requires examining each composed resource's status independently).
TL;DR
Crossplane resources are read via the Kubernetes API using dynamically registered API groups. Providers register their ManagedResource CRDs at installation — an AWS provider registers s3.aws.upbound.io, rds.aws.upbound.io, etc. Read Provider health at GET /apis/pkg.crossplane.io/v1/providers/{name} — look for status.conditions with type: Installed and type: Healthy. Read ProviderConfig health at GET /apis/aws.upbound.io/v1beta1/providerconfigs/{name} (API group varies by provider). ManagedResource health uses a two-condition model: type: Synced (True = desired state sent to provider API without error) and type: Ready (True = cloud resource is in the desired state). The most dangerous state is Synced: True, Ready: False — the provider received the request but the resource is not yet operational (common during long-provisioning operations). Synced: False = the provider got an error from the cloud API (quota exceeded, invalid parameter, insufficient permissions) — read status.conditions[Synced].message for the specific cloud API error.
Crossplane architecture for MCP tool authors
Crossplane itself is a core controller deployment. Providers are installed as separate controller pods via the Provider custom resource — each Provider manages a specific cloud or service (provider-aws, provider-gcp, provider-azure, provider-helm, provider-kubernetes, etc.). A Provider installs its own CRDs for ManagedResources and watches those CRDs to reconcile actual cloud state against the declared desired state.
The API group structure requires knowing which Provider manages which resource type. For the Upbound official provider family: provider-aws-s3 installs bucket.s3.aws.upbound.io; provider-aws-rds installs instance.rds.aws.upbound.io. For Community Crossplane providers: provider-aws installs bucket.s3.aws.crossplane.io (different API group suffix). Always check spec.package in the Provider resource to identify which provider family is installed before constructing API paths.
async function createCrossplaneClient(k8sBaseUrl, token) {
const headers = {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json',
};
const get = async (path) => {
const res = await fetch(`${k8sBaseUrl}${path}`, { headers });
if (!res.ok) throw new Error(`K8s API ${path} → ${res.status}`);
return res.json();
};
// List all installed Crossplane Providers
async function listProviders() {
const result = await get('/apis/pkg.crossplane.io/v1/providers');
return (result.items ?? []).map(parseProvider);
}
// Get specific Provider health
async function getProvider(name) {
const provider = await get(`/apis/pkg.crossplane.io/v1/providers/${name}`);
return parseProvider(provider);
}
// List CompositeResourceDefinitions (XRDs) — defines the schema of Composite Resources
async function listXRDs() {
const result = await get('/apis/apiextensions.crossplane.io/v1/compositeresourcedefinitions');
return result.items ?? [];
}
// Discover all CRDs registered by Crossplane providers
async function listProviderCRDs() {
const crds = await get('/apis/apiextensions.k8s.io/v1/customresourcedefinitions');
// Crossplane-managed CRDs have a label crossplane.io/managed-by=crossplane
return (crds.items ?? []).filter(crd =>
crd.metadata?.labels?.['crossplane.io/managed-by'] === 'crossplane' ||
crd.metadata?.labels?.['pkg.crossplane.io/revision'] !== undefined
);
}
return { listProviders, getProvider, listXRDs, listProviderCRDs };
}
Provider conditions: Installed vs Healthy and ProviderRevision tracking
A Crossplane Provider has two distinct health conditions: type: Installed (the Provider package was pulled and all CRDs were successfully installed into the cluster) and type: Healthy (the Provider controller pod is running and passing readiness checks). These can diverge: Installed: True, Healthy: False means the CRDs are installed but the provider pod crashed or is in CrashLoopBackOff. This is the most dangerous state — ManagedResources will be accepted by the API server (CRDs exist) but the controller is not reconciling them.
When a Provider version is upgraded, Crossplane creates a ProviderRevision resource for the new version. The old ProviderRevision remains inactive. The spec.revisionActivationPolicy controls whether new revisions activate automatically (Automatic, default) or require manual approval (Manual). If a Provider upgrade is breaking, look at the inactive ProviderRevision's conditions to diagnose the failure before it activates.
function parseProvider(provider) {
const conditions = provider.status?.conditions ?? [];
const installedCondition = conditions.find(c => c.type === 'Installed');
const healthyCondition = conditions.find(c => c.type === 'Healthy');
return {
name: provider.metadata.name,
packageImage: provider.spec?.package, // e.g. "xpkg.upbound.io/upbound/provider-aws-s3:v1.2.0"
// Installation status
installed: installedCondition?.status === 'True',
installedReason: installedCondition?.reason ?? null,
installedMessage: installedCondition?.message ?? null,
// Controller pod health
healthy: healthyCondition?.status === 'True',
healthyReason: healthyCondition?.reason ?? null,
healthyMessage: healthyCondition?.message ?? null,
// Most dangerous state: CRDs installed but controller not running
crdsPresentButControllerDown: installedCondition?.status === 'True' && healthyCondition?.status !== 'True',
currentRevision: provider.status?.currentRevision ?? null,
desiredRevision: provider.spec?.revision ?? null,
revisionActivationPolicy: provider.spec?.revisionActivationPolicy ?? 'Automatic',
};
}
// Parse any Crossplane ManagedResource (generic — works for all provider CRDs)
function parseManagedResource(resource) {
const conditions = resource.status?.conditions ?? [];
const syncedCondition = conditions.find(c => c.type === 'Synced');
const readyCondition = conditions.find(c => c.type === 'Ready');
// External name: the actual cloud resource identifier
const externalName = resource.metadata?.annotations?.['crossplane.io/external-name'] ??
resource.status?.atProvider?.id ??
resource.status?.atProvider?.arn ??
null;
const managementPolicies = resource.spec?.managementPolicies ?? ['*'];
const isPaused = managementPolicies.includes('Observe') &&
!managementPolicies.includes('Create') &&
!managementPolicies.includes('Update');
return {
name: resource.metadata.name,
kind: resource.kind,
apiVersion: resource.apiVersion,
externalName,
// ProviderConfig reference
providerConfigRef: resource.spec?.providerConfigRef?.name ?? 'default',
// Two-condition model
synced: syncedCondition?.status === 'True',
syncedReason: syncedCondition?.reason ?? null,
syncedMessage: syncedCondition?.message ?? null, // Cloud API error text if Synced: False
ready: readyCondition?.status === 'True',
readyReason: readyCondition?.reason ?? null,
readyMessage: readyCondition?.message ?? null,
// Health state interpretation
// Synced: True, Ready: False = provisioning in progress (normal for slow resources)
// Synced: False = cloud API returned error — read syncedMessage
// Synced: True, Ready: True = resource operational
healthState: (
syncedCondition?.status !== 'True' ? 'api_error' :
readyCondition?.status !== 'True' ? 'provisioning' :
'operational'
),
isPaused,
managementPolicies,
// Deletion policy: what happens to the cloud resource when the K8s object is deleted
deletionPolicy: resource.spec?.deletionPolicy ?? 'Delete', // 'Delete' | 'Orphan'
};
}
CompositeResources and Compositions: tracing the dependency chain
A CompositeResource (XR) is a high-level abstraction defined by a CompositeResourceDefinition (XRD). When a Claim creates an XR, Crossplane applies a Composition to expand it into one or more ManagedResources. The XR's status.conditions with type: Ready aggregates the readiness of all composed resources — Ready: False on the XR does not tell you which ManagedResource is failing.
To trace the dependency chain, read status.resources on the XR — this array lists each composed resource by name and API version. For each composed resource, fetch its individual status. A common pattern: an XR with five composed resources (VPC, Subnet, SecurityGroup, IAM Role, RDS Instance) where the RDS Instance has Synced: True, Ready: False (provisioning) while all others have Ready: True. The XR will show Ready: False aggregating this state — but the XR itself is not failing, it's waiting for the database to become available.
async function traceCompositeResourceHealth(k8sClient, xrApiVersion, xrKind, namespace, xrName) {
// Fetch the Composite Resource (or Claim)
const apiGroup = xrApiVersion.split('/')[0];
const apiVer = xrApiVersion.split('/')[1];
const pluralKind = xrKind.toLowerCase() + 's'; // approximate; use API discovery for exact plural
const xrPath = namespace
? `/apis/${apiGroup}/${apiVer}/namespaces/${namespace}/${pluralKind}/${xrName}`
: `/apis/${apiGroup}/${apiVer}/${pluralKind}/${xrName}`;
const xr = await k8sClient.getResource(xrPath);
const xrConditions = xr.status?.conditions ?? [];
const xrReady = xrConditions.find(c => c.type === 'Ready');
const xrSynced = xrConditions.find(c => c.type === 'Synced');
// Get composed resource references
const composedResourceRefs = xr.status?.resources ?? [];
// Also check spec.resourceRefs for XRs (older Crossplane versions)
const specResourceRefs = xr.spec?.resourceRefs ?? [];
const allRefs = [...composedResourceRefs, ...specResourceRefs];
// Fetch each composed resource in parallel
const composedResources = await Promise.allSettled(
allRefs.map(async (ref) => {
const [refGroup, refVer] = (ref.apiVersion ?? '').split('/');
const refPlural = (ref.kind ?? '').toLowerCase() + 's';
const resource = await k8sClient.getResource(
`/apis/${refGroup}/${refVer}/${refPlural}/${ref.name}`
);
return parseManagedResource(resource);
})
);
const resolved = composedResources
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
const failing = resolved.filter(r => r.healthState === 'api_error');
const provisioning = resolved.filter(r => r.healthState === 'provisioning');
const operational = resolved.filter(r => r.healthState === 'operational');
return {
compositeResource: {
name: xrName,
kind: xrKind,
ready: xrReady?.status === 'True',
synced: xrSynced?.status === 'True',
readyMessage: xrReady?.message ?? null,
},
composedResources: {
total: resolved.length,
operational: operational.length,
provisioning: provisioning.length,
failing: failing.length,
},
// Root cause: the leaf resources with API errors
rootCauses: failing.map(r => ({
kind: r.kind,
name: r.name,
error: r.syncedMessage, // The actual cloud API error (quota, auth, invalid param)
})),
// Pending resources slowing provisioning
provisioningResources: provisioning.map(r => ({
kind: r.kind,
name: r.name,
externalName: r.externalName,
})),
};
}
Management policies and deletion policies for safe MCP operations
Crossplane v1.14+ introduced management policies to control which lifecycle operations Crossplane performs on the cloud resource. The spec.managementPolicies field is an array that can include Observe, Create, Update, Delete, LateInitialize, or the shorthand * (all). The policy ["Observe"] (observe-only mode) makes Crossplane import and track an existing cloud resource without managing its lifecycle — no create, no update, no delete. MCP tools querying Crossplane should surface the management policy to clarify whether a Ready: False resource is a Crossplane provisioning problem or an observed resource that is externally broken.
The spec.deletionPolicy field controls what happens to the actual cloud resource when the Kubernetes object is deleted: Delete (default) deletes the cloud resource; Orphan removes the Crossplane management but leaves the cloud resource running. Always surface this field in MCP tool responses — an operator intending to "clean up the Kubernetes object" with deletionPolicy: Delete will destroy production infrastructure.
// Safe delete-check: surface deletionPolicy before any delete recommendation
function assessDeleteSafety(managedResource) {
return {
name: managedResource.name,
kind: managedResource.kind,
externalName: managedResource.externalName,
deletionPolicy: managedResource.deletionPolicy,
// Warning for MCP tool responses:
warning: managedResource.deletionPolicy === 'Delete'
? `CAUTION: Deleting this Kubernetes object will DELETE the actual cloud resource (${managedResource.externalName}). Set spec.deletionPolicy: Orphan first to preserve the cloud resource.`
: `Deletion policy is Orphan — deleting this object will NOT delete the cloud resource (${managedResource.externalName}).`,
safeToDeleteKubernetesObject: managedResource.deletionPolicy === 'Orphan',
};
}
// Aggregate all ManagedResources with Synced: False (cloud API errors)
async function findCrossplaneErrors(k8sClient, providerCRDs) {
const errorsByKind = {};
await Promise.allSettled(
providerCRDs.map(async (crd) => {
const group = crd.spec.group;
const version = crd.spec.versions.find(v => v.served && v.storage)?.name;
const plural = crd.spec.names.plural;
if (!version) return;
try {
const resources = await k8sClient.getResource(
`/apis/${group}/${version}/${plural}`
);
for (const item of resources.items ?? []) {
const parsed = parseManagedResource(item);
if (parsed.healthState === 'api_error') {
const key = parsed.kind;
errorsByKind[key] = errorsByKind[key] ?? [];
errorsByKind[key].push({
name: parsed.name,
error: parsed.syncedMessage,
providerConfig: parsed.providerConfigRef,
});
}
}
} catch {
// Skip CRDs where list permissions are absent
}
})
);
return errorsByKind;
}
AliveMCP integration for Crossplane health monitoring
Crossplane health monitoring requires covering three layers: Provider health (if a provider pod is down, all ManagedResources of that type stop reconciling), ProviderConfig credential validity (if credentials rotate or expire, every ManagedResource referencing that ProviderConfig will fail with an authentication error on the next reconcile), and individual ManagedResource Synced: False conditions (cloud API errors from quota exhaustion, service limits, or IAM permission changes). A healthy Provider pod with valid credentials does not guarantee ManagedResources are reconciling — the cloud API may be rejecting requests for reasons that are resource-specific.
async function crossplaneHealthProbe(k8sBaseUrl, token) {
const headers = { 'Authorization': `Bearer ${token}`, 'Accept': 'application/json' };
const get = path => fetch(`${k8sBaseUrl}${path}`, { headers }).then(r => r.json());
// Layer 1: Crossplane core controller
const coreDeploy = await get(
'/apis/apps/v1/namespaces/crossplane-system/deployments/crossplane'
);
const coreReady = (coreDeploy.status?.readyReplicas ?? 0) > 0;
// Layer 2: All Provider health
const providers = await get('/apis/pkg.crossplane.io/v1/providers');
const parsedProviders = (providers.items ?? []).map(parseProvider);
const unhealthyProviders = parsedProviders.filter(p => !p.healthy || !p.installed);
const dangerousProviders = parsedProviders.filter(p => p.crdsPresentButControllerDown);
// Layer 3: Scan for Synced: False ManagedResources
// (abbreviated — in practice enumerate provider CRDs dynamically)
const crds = await get(
'/apis/apiextensions.k8s.io/v1/customresourcedefinitions' +
'?labelSelector=crossplane.io%2Fmanaged-by%3Dcrossplane'
);
let syncErrors = 0;
const errorSample = [];
// Sample check on known-common CRD groups (expand with dynamic CRD discovery in production)
await Promise.allSettled(
(crds.items ?? []).slice(0, 20).map(async (crd) => {
const group = crd.spec.group;
const version = crd.spec.versions.find(v => v.served && v.storage)?.name;
const plural = crd.spec.names.plural;
if (!version) return;
try {
const resources = await get(`/apis/${group}/${version}/${plural}`);
for (const item of resources.items ?? []) {
const synced = item.status?.conditions?.find(c => c.type === 'Synced');
if (synced?.status === 'False') {
syncErrors++;
if (errorSample.length < 5) {
errorSample.push({
kind: item.kind,
name: item.metadata.name,
error: synced.message,
});
}
}
}
} catch { /* skip CRDs requiring elevated permissions */ }
})
);
return {
healthy: coreReady && dangerousProviders.length === 0 && syncErrors === 0,
coreControllerReady: coreReady,
providers: {
total: parsedProviders.length,
healthy: parsedProviders.filter(p => p.healthy).length,
unhealthy: unhealthyProviders.length,
crdsPresentButControllerDown: dangerousProviders.length,
unhealthyDetails: unhealthyProviders.map(p => ({
name: p.name,
healthyReason: p.healthyReason,
installedReason: p.installedReason,
})),
},
managedResources: {
syncErrorCount: syncErrors,
errorSample,
},
};
}
Register this probe with AliveMCP at a 5-minute interval with a 20-second timeout (CRD enumeration can be slow on large clusters). Configure alert severity: Crossplane core controller down = P1 (no composition reconciliation); Provider crdsPresentButControllerDown = P1 (that provider's ManagedResources accepted by API but not reconciling — silent failure); ManagedResource Synced: False = P2 with cloud API error detail in alert body.
Related guides
- MCP tools for Kubernetes API — Deployment rollout status, Pod conditions, and Event-based failure diagnosis
- MCP tools for Flux CD — GitRepository reconciliation, Kustomization health, and HelmRelease status
- MCP tools for Terraform — plan/apply status, state file health, and workspace management
- MCP tools for External Secrets Operator — SecretStore health and ExternalSecret sync status
- MCP tools for AWS — IAM, service health, and resource status across accounts
- MCP server health check patterns — composite endpoints and failure classification