Skip to content

MCP: X-hal0-Private: 1 header does not actually scope writes to private namespace #413

@thinmintdev

Description

@thinmintdev

Summary

The MCP transport documents X-hal0-Private: 1 as the way to scope memory_add writes to the caller's private:<client_id> namespace. In practice, writes still land in the shared dataset.

Reproduction

Initialize an MCP session against /mcp/memory/mcp with X-hal0-Private: 1 and no bearer (so client_id == "anonymous"), then call:

{
  "name": "memory_add",
  "arguments": {
    "args": {"text": "private-test", "dataset": ""}
  }
}

Inspect the resulting record: dataset == "shared", not "private:anonymous". The behaviour is identical with X-hal0-Private: 1 and without.

Confirmed across two records:

  • ca49afd2-... — sent with X-hal0-Private: 1
  • d724a86d-... — sent without

Both landed in shared. (Cleaned up after testing.)

Suggested fix area

  • /opt/hal0/src/hal0/api/mcp_mount.py:MCPAuthMiddleware parses the header into the request _caller contextvar, but
  • by the time /opt/hal0/src/hal0/memory/cognee_wrapper.py:_effective_write_dataset is called inside the FastMCP tool handler, the contextvar appears to have been dropped — likely because the Starlette BaseHTTPMiddleware task context doesn't propagate to the MCP session's anyio task group.

The fix is either to attach caller.private to the MCP session state explicitly during initialize, or to read the header out of request.headers directly in the tool handler.

Workaround

Always pass dataset: "private:hermes-agent" (or whatever your agent's namespace is) explicitly in every memory_add call. We've encoded this in our hermes config as a force_write_dataset per-server hint and documented it as a hard convention.

Environment

  • hal0 v0.3.0a1, CT 105
  • Discovered while wiring hermes-agent's hal0-memory MCP client

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions