# Aeonora MCP Server

Status: Implemented. Verified over real stdio JSON-RPC by `packages/mcp/self-check.mjs`, and connected as a live stdio server by the Claude Code MCP client.

One MCP server reaches every MCP-capable harness, so onboarding is a config entry rather than a per-harness adapter install. The four harness adapters remain the *import* path; MCP is the *live* path.

## Install

One command finds your harness, encrypts its Markdown brain, and prints the config to paste:

```bash
npx aeonora setup
```

Then register the server with any MCP client. Claude Code:

```bash
npm install -g aeonora

claude mcp add aeonora \
  -e AEONORA_WORKSPACE=$HOME/.aeonora/agent \
  -- aeonora-mcp
```

Or by config file:

```json
{
  "mcpServers": {
    "aeonora": {
      "command": "aeonora-mcp",
      "env": { "AEONORA_WORKSPACE": "~/.aeonora/agent" }
    }
  }
}
```

`setup` is idempotent — run it again and it reports the existing agent instead of creating a second one.

Then, in the agent: **"call describe"**, and if it reports `connected: false`, **"import_brain from hermes"** (or `openclaw`, `nanoclaw`, `openhands`, `claude-code`, `codex`, `cursor`, `obsidian`, `markdown`, or an absolute path).

`describe` also reports the active policy preset and which actions are allowed, need owner approval, or are denied — see [governance](governance.md).

## Environment

| Variable | Default | Meaning |
|---|---|---|
| `AEONORA_WORKSPACE` | `.aeonora/agent` | Workspace holding `state.json`, `manifest.json`, and the local keys |
| `AEONORA_BLOB_STORE` | `file` | `file` or `ipfs` — where the **ciphertext** goes |
| `IPFS_API_URL` | — | Required when `AEONORA_BLOB_STORE=ipfs` |
| `AEONORA_MCP_READONLY` | — | `1` removes every write tool from `tools/list` and refuses write calls |

## Tools

| Tool | Writes | Governed action | Purpose |
|---|---|---|---|
| `describe` | | — | State, policy, capabilities, and an honest security summary. Call first. |
| `import_brain` | ● | `import-memory` | Capture a brain's Markdown, encrypt, register. Run once. |
| `query` | | `read-memory` | Lexical search over the decrypted brain |
| `read_note` | | `read-memory` | Read one Markdown file by path |
| `list_notes` | | `read-memory` | Every path and size |
| `remember` | ● | `write-memory` | Append a durable note, re-encrypt, record the new commitment |
| `restore_to` | ● | `export-memory` | Decrypt into a directory so another harness can pick it up |
| `verify` | | — | Registry, manifest, and blob still agree |

`describe` and `verify` return metadata rather than brain content and are not gated. Every other tool resolves to a governed action, is decided against the owner's policy, and appends a receipt whether it ran or not. Under the default `guarded` preset, reads flow and writes and exports are refused until the owner runs `aeonora grant <action>`.

## Security model

The server is **stdio only**. There is no HTTP mode, and adding one would break the guarantee: the process that unwraps the brain key must be the owner's process, on the owner's machine. It is spawned by the harness, unwraps the key in memory, decrypts locally, and answers from that plaintext. Only ciphertext reaches the blob store.

Stated precisely, because the difference matters:

- **True:** the brain is encrypted at rest with AES-256-GCM under a key that only exists on this machine; the blob store — local disk, IPFS, or the hosted vault — holds ciphertext and nothing else; every content change moves the commitment; revoking access and restoring elsewhere does not change the agent ID.
- **Not claimed:** answers returned by `query` and `read_note` enter the model's context window and therefore reach your model provider. Aeonora protects custody and portability. It does not hide your memory from the model you chose to run.

No tool returns a private key, the data key, the wrapped brain key, or recovery words; `packages/mcp/self-check.mjs` asserts that the whole tool transcript is free of them.

A third statement belongs beside the two above, because governance has its own boundary: `restore_to` writes decrypted Markdown to disk. That export is gated and receipted, and `describe` reports `plaintextExportsOnDisk` and `lastExport` — but once those files exist Aeonora cannot apply policy to them. The same is true of the `.aeonora/brain/` copy an adapter install writes into a harness, which is why the bridge file it leaves says so.

Retrieval is **lexical** — term frequency over Markdown, not embeddings. `describe` reports `retrieval: "lexical"` so an agent does not assume semantic recall it will not get.

## Verification

```bash
node packages/mcp/self-check.mjs
```

The check spawns the real CLI and speaks real stdio JSON-RPC: `initialize` handshake and protocol-version echo, `tools/list` shape, cold `describe`, import, query, read, `remember` with a commitment change, `verify`, restore to disk, key-material leakage, tool-level error handling, unknown-method JSON-RPC errors, and read-only enforcement.

Governance is exercised in the same run: that `guarded` refuses an ungranted write, that the refused write never reaches the brain, that a single-use grant is spent after one write, that a `deny` preset offers no approval path, that plaintext exports are disclosed by `describe`, and that receipts chain and record shape rather than content. The decision table itself is covered by `node packages/aeonora/governance-self-check.mjs`.
