Skip to content

Authentication

The Agent Protocol has one job at connect time: prove that the process on the other end of the socket belongs to the same user that launched Maho. There is no username, no password, no OAuth flow. The operating system is the source of truth for identity.

Maho binds a Unix domain socket at:

~/Library/Application Support/Maho/maho.sock

On accept, the server queries the peer’s credentials via LOCAL_PEERCRED (getsockopt with SOL_LOCAL / LOCAL_PEERCRED). If the peer’s effective UID does not match the browser’s own UID, the connection is closed before the first byte is read.

The socket file is created with mode 0600, so even before the peer-credential check, the filesystem refuses open() for anyone but the owner.

Every session begins with an initialize request. Servers that speak MCP already expect this call, so any conforming MCP client will produce it for you. If you are writing a client from scratch, this is the shape.

{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "example-agent",
"version": "0.1.0"
}
}
}

On Windows, the client also includes the decrypted session token:

{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": { "name": "example-agent", "version": "0.1.0" },
"sessionToken": "b3a7...redacted..."
}
}

The maho mcp CLI handles the token read, decrypt, and echo automatically. A raw client has to do it itself.

{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": { "listChanged": false }
},
"serverInfo": {
"name": "maho-browser-mcp",
"version": "0.1.0"
}
}
}

After a successful response, the client sends notifications/initialized, and the session is ready to call tools.

Authentication failures come back as standard JSON-RPC 2.0 error objects. The relevant codes:

| Code | Name | When it fires | |------|------|---------------| | -32001 | not_authenticated | A tool call arrived before initialize completed, or the session was invalidated. | | -32002 | authentication_failed | Peer credential mismatch (Unix), missing or wrong session token (Windows), or DPAPI decrypt failure. |

Example error frame:

{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32002,
"message": "authentication_failed",
"data": { "reason": "peer_uid_mismatch" }
}
}

On -32002 the server closes the connection after sending the response. On -32001 the connection stays open and the client can retry initialize.

  • Per-tool authorization. Every tool available to the server is available to any authenticated session. There is no role or scope model. Access control lives one layer up in the security model (leases, allowed domains, credential firewall).
  • Rate limiting. Not enforced at the auth layer. The browser will still refuse individual tool calls that violate the session policy.
  • Multi-agent identity. All sessions with a valid peer credential are equal peers. If you need to distinguish agents, put an identifier in clientInfo.name and inspect the audit log yourself.
  • Tools reference for what an authenticated session can call.
  • Security model for the constraints applied to every tool call after authentication.