astrid-runtime/astrid · error
system-resident capsule
Error message
system-resident capsule '{id}' cannot host principal-bearing stdio MCP servers What it means
Capsules classified as system-resident (operator-approved, shared runtime) are forbidden from declaring stdio MCP servers, because those servers run with a principal's identity and would break the isolation of a shared system runtime. The kernel throws this during load when `classify_runtime_residency` returns system residency and `manifest.mcp_servers` is non-empty.
Solutions
- Remove the `mcp_servers` entries from the capsule's Capsule.toml.
- Remove the capsule id from the operator's `system_capsules` admission set so it runs as a principal-scoped runtime.
- Split the MCP servers into a separate principal-scoped capsule.
Example fix
# before (Capsule.toml)
[mcp_servers]
stdio = { command = "./mcp.sh" }
# after
# [mcp_servers] section removed for system-resident capsule Defensive patterns
Strategy: validation
Validate before calling
let manifest = astrid_capsule::discovery::load_manifest(&dir.join("Capsule.toml"))?;
if is_system_capsule(&manifest.package.name) && !manifest.mcp_servers.is_empty() {
return Err(anyhow!("system-resident capsule cannot declare mcp_servers"));
} Try / catch
if let Err(e) = kernel.load_capsule(&dir, &principal).await {
if e.to_string().contains("cannot host principal-bearing stdio MCP servers") {
strip_mcp_servers_from_manifest(&dir)?;
return kernel.load_capsule(&dir, &principal).await;
}
return Err(e);
} Prevention
- Lint Capsule.toml for mcp_servers before adding a capsule to the system list.
- Keep MCP servers in dedicated principal-scoped capsules.
- Validate manifests in CI against residency classification.
When it happens
Trigger: Loading a capsule whose Capsule.toml declares `[mcp_servers]` entries while the capsule id is listed in `system_capsules` (or otherwise classified system-resident).
Common situations: An operator promotes a capsule with MCP servers to the system capsule list; a Capsule.toml is copied into a system-resident deployment without stripping MCP server entries; residency classification changed after the manifest was written.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- durable capsule component path is not UTF-8
- live replacement of stdio MCP capsule
- replacement manifest id
- system-resident capsule
- workspace capsule component must be relative
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/3d87206a77075876.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/lib.rs:1736
principal,
&manifest,
&bound.snapshot,
)?;
}
if *principal != PrincipalId::default()
&& self.capabilities.is_principal_retiring(principal).await
{
anyhow::bail!("cannot load capsule '{id}' for retiring principal '{principal}'");
}
let wasm_hash = capsule_instance_hash(&manifest, &runtime_dir);
// `capabilities.uplink` alone remains a principal-scoped daemon/host
// grant unless the operator explicitly promotes it. A manifest that
// actually provides an uplink must be operator-approved.
let system_allowed = self.system_capsules.read().await.contains(id.as_str());
let system_runtime =
classify_runtime_residency(&manifest, &id, system_allowed)?.is_system();
if system_runtime && !manifest.mcp_servers.is_empty() {
anyhow::bail!(
"system-resident capsule '{id}' cannot host principal-bearing stdio MCP servers"
);
}
self.verify_workspace_capsule_tree(&runtime_dir)?;
// Mutable runtimes are authority-scoped. A principal always receives a
// fresh runtime for its immutable UID; only an explicitly classified
// SystemResident service may attach another view to one runtime.
{
let mut registry = self.capsules.write().await;
if registry.get_for(principal, &id).is_some() {
return Ok(());
}
if system_runtime && registry.contains_system_runtime(&id, &wasm_hash) {
registry
.register_existing(&id, &wasm_hash, principal)
.map_err(|e| anyhow::anyhow!("Failed to add capsule view: {e}"))?;
if let Some(capsule) = registry.get_for(principal, &id) {View on GitHub (pinned to affd8760f4)