{"record":{"id":"c7b13f24aeec753f","repo":"astrid-runtime/astrid","slug":"could-not-authenticate-as-principal-caller-for","errorCode":null,"errorMessage":"could not authenticate as principal '{caller}' for `astrid mcp serve`: no keypair found (keys/{caller}.key), so the daemon would bind this connection to the no-capability `anonymous` identity. Every tool call would then fail the ingress-trust and capability checks and appear to hang. Refusing to serve the MCP bridge as `anonymous`.\n\nFix: run `astrid agent create {caller}` to mint its keypair (or back-fill an existing keyless principal's), then retry. To serve unauthenticated on purpose, pass `--principal anonymous` before `mcp serve`.","messagePattern":"could not authenticate as principal '\\{caller\\}' for `astrid mcp serve`: no keypair found \\(keys/\\{caller\\}\\.key\\), so the daemon would bind this connection to the no-capability `anonymous` identity\\. Every tool call would then fail the ingress-trust and capability checks and appear to hang\\. Refusing to serve the MCP bridge as `anonymous`\\.\n\nFix: run `astrid agent create \\{caller\\}` to mint its keypair \\(or back-fill an existing keyless principal's\\), then retry\\. To serve unauthenticated on purpose, pass `--principal anonymous` before `mcp serve`\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/mcp/mod.rs","lineNumber":141,"sourceCode":"/// identity.\n///\n/// `astrid --principal X mcp serve` connects, but if `X` has no keypair the\n/// handshake falls to the legacy single-frame path and the daemon stamps the\n/// connection `anonymous`. The bridge would then come up \"successfully\" yet\n/// every `tools/call` fails the ingress-trust and capability checks — to a\n/// client it just hangs/times out, with no hint why. This turns that silent,\n/// confusing failure into a loud, actionable error at startup.\n///\n/// Requesting `anonymous` explicitly (`astrid --principal anonymous mcp serve`) is allowed:\n/// serving unauthenticated is then a deliberate choice, not an accident.\nfn require_authenticated_unless_anonymous(\n    caller: &astrid_core::PrincipalId,\n    authenticated: bool,\n) -> Result<()> {\n    if authenticated || *caller == astrid_core::PrincipalId::anonymous() {\n        return Ok(());\n    }\n    anyhow::bail!(\n        \"could not authenticate as principal '{caller}' for `astrid mcp serve`: \\\n         no keypair found (keys/{caller}.key), so the daemon would bind this \\\n         connection to the no-capability `anonymous` identity. Every tool call \\\n         would then fail the ingress-trust and capability checks and appear to \\\n         hang. Refusing to serve the MCP bridge as `anonymous`.\\n\\n\\\n         Fix: run `astrid agent create {caller}` to mint its keypair (or \\\n         back-fill an existing keyless principal's), then retry. To serve \\\n         unauthenticated on purpose, pass `--principal anonymous` before `mcp serve`.\"\n    );\n}\n\n/// Explicit `anonymous` MCP is a transport-only, no-capability mode and has no\n/// broker capsule to prove. Named principals must always prove their broker\n/// front door before stdio is exposed.\nfn broker_readiness_required(caller: &astrid_core::PrincipalId) -> bool {\n    *caller != astrid_core::PrincipalId::anonymous()\n}\n","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/mcp/mod.rs#L123-L159","documentation":"Before serving the MCP bridge, require_authenticated_unless_anonymous refuses to start when the caller principal is not authenticated and has no keypair on disk (keys/{caller}.key). Without a keypair the daemon would bind the connection to the capability-less `anonymous` identity, causing every tool call to fail ingress-trust/capability checks and appear to hang. The error is deliberately verbose and actionable.","triggerScenarios":"Running `astrid mcp serve --principal <name>` (or the library call) where <name> is not anonymous, authenticated=false (no keypair at keys/{name}.key), so serve() invokes this guard and bails.","commonSituations":"Principal created conceptually but `astrid agent create` never run; keypair deleted or keys/ directory lost; typo in the principal name so no key file matches; migrating machines without copying keys/.","solutions":["Run `astrid agent create <principal>` to mint the missing keypair, then retry `astrid mcp serve`.","If the principal already exists keylessly, back-fill its keypair with the agent-create command as instructed.","If unauthenticated serving is intended, pass `--principal anonymous` explicitly before `mcp serve`.","Verify the exact principal name and that keys/{principal}.key exists relative to the expected keys directory."],"exampleFix":"// before\nastrid mcp serve --principal alice   # no keys/alice.key\n// after\nastrid agent create alice\nastrid mcp serve --principal alice","handlingStrategy":"validation","validationCode":"let caller = principal_name;\nlet key_path = std::path::Path::new(\"keys\").join(format!(\"{caller}.key\"));\nif caller != \"anonymous\" && !key_path.exists() {\n    eprintln!(\"run: astrid agent create {caller}\");\n    std::process::exit(1);\n}","typeGuard":"fn can_serve(caller: &str, authenticated: bool) -> bool {\n    authenticated || caller == \"anonymous\"\n        || std::path::Path::new(\"keys\").join(format!(\"{caller}.key\")).exists()\n}","tryCatchPattern":"if let Err(e) = serve(principal, workspace).await {\n    if e.to_string().contains(\"could not authenticate as principal\") {\n        // message already includes the fix command; surface it verbatim to the user\n        eprintln!(\"{e}\");\n    }\n}","preventionTips":["Run `astrid agent create <principal>` before serving for any named principal","Back up and migrate the keys/ directory with workspaces","Verify principal-name spelling matches an existing keypair","Use --principal anonymous only deliberately"],"tags":["authentication","mcp","keypair","identity"],"backgroundTag":"missing-credentials","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}