{"record":{"id":"6be5d00f263cabd3","repo":"unicity-aos/aos-ce","slug":"aos-mcp-malformed-expected-host-hook-ingress-error","errorCode":null,"errorMessage":"aos-mcp: malformed {expected_host} hook ingress: {error}","messagePattern":"aos-mcp: malformed (.+?) hook ingress: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"capsules/capsule-mcp/src/host_hooks.rs","lineNumber":69,"sourceCode":"    host: String,\n    session_id: String,\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    canonical_hook: Option<String>,\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    event: Option<String>,\n    correlation_id: String,\n    route_id: String,\n    delivery_id: String,\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    context: Option<String>,\n}\n\npub(crate) fn handle(expected_host: &str, payload: serde_json::Value) -> Result<(), SysError> {\n    let request: HostHookRequest = match serde_json::from_value(payload) {\n        Ok(request) => request,\n        Err(error) => {\n            reject(expected_host, \"unknown\", \"malformed_payload\");\n            log::warn(format!(\n                \"aos-mcp: malformed {expected_host} hook ingress: {error}\"\n            ));\n            return Ok(());\n        }\n    };\n    if let Err(reason) = validate_request(expected_host, &request) {\n        reject(expected_host, &request.event, reason);\n        return Ok(());\n    }\n\n    let caller = match runtime::caller() {\n        Ok(caller) => caller,\n        Err(error) => {\n            reject(expected_host, &request.event, \"caller_unavailable\");\n            log::warn(format!(\n                \"aos-mcp: caller unavailable for {expected_host} hook: {error}\"\n            ));\n            return Ok(());","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/unicity-aos/aos-ce/blob/f6f22024fb1e8d122f28a1b4a9f75aee448ae839/capsules/capsule-mcp/src/host_hooks.rs#L51-L87","documentation":"The MCP capsule's host-hook ingress handler (`handle`) deserializes the incoming payload into HostHookRequest. When serde_json cannot parse the payload, the hook is rejected as `malformed_payload`, a warning is logged naming the host and serde error, and the handler returns Ok(()) — the payload is dropped, not executed.","triggerScenarios":"Host sends a hook ingress payload to handle(expected_host, payload) that is not valid JSON for HostHookRequest (missing required fields like event/principal_id, wrong types, or invalid JSON).","commonSituations":"Host bridge version drift producing a different payload schema, manual curl testing against the hook with a hand-written body, or serialization bugs in the sending side.","solutions":["Inspect the logged serde error and fix the payload to match HostHookRequest's fields and types.","Ensure required fields (event, principal_id) are present and correctly typed in the ingress payload.","Verify the sending host bridge version matches the capsule's expected hook schema."],"exampleFix":"// before (host side)\nsend_hook(host, json!({ \"event\": \"tool_call\" }))\n// after\nsend_hook(host, json!({ \"event\": \"tool_call\", \"principal_id\": \"abc-123\" }))","handlingStrategy":"try-catch","validationCode":"// Host side: validate payload shape before sending\nif payload.get(\"principal_id\").is_none() || payload.get(\"event\").is_none() {\n    panic!(\"hook ingress payload missing required fields\");\n}","typeGuard":"fn is_valid_hook_payload(v: &serde_json::Value) -> bool {\n    v.get(\"event\").and_then(|e| e.as_str()).is_some()\n        && v.get(\"principal_id\").and_then(|p| p.as_str()).is_some()\n}","tryCatchPattern":"match serde_json::from_value::<HostHookRequest>(payload) {\n    Ok(req) => handle(req),\n    Err(e) => log::warn!(\"malformed hook ingress: {e}\"),\n}","preventionTips":["Share the HostHookRequest struct/types between host bridge and capsule.","Add contract tests that serialize and deserialize sample hook payloads.","Monitor the malformed_payload rejection metric for schema drift."],"tags":["serde","json","validation","host-hooks","mcp"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"f6f22024fb1e8d122f28a1b4a9f75aee448ae839","analyzedAt":"2026-09-13T03:04:44.565Z","contentChangedAt":"2026-09-13T03:04:44.565Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}