# Connect an agent

Use the stdio or HTTP MCP server, the repository plugins or an installed bundle with the agent's own Lys proof.

An agent uses eleven typed MCP tools over a running store. Standard input and output serve one host process. HTTP serves clients through a loopback listener and TLS proxy. Both present the caller's own pass to the store.

## Before you begin

Have the store running and know its service configuration or socket. For a protected store, first complete [Sign-in and permissions](/haematite/concepts/sign-in-and-permissions/). The MCP adapter does not open a data directory or start a service.

## Standard input and output

Register the command with the host. This registration reads a pass file before each tool call.

**MCP registration with a pass file**

```json
{
  "mcpServers": {
    "haematite": {
      "command": "haematite",
      "args": [
        "mcp",
        "--config",
        "/etc/haematite/service.json",
        "--pass-file",
        "/etc/haematite/agent-pass"
      ]
    }
  }
}
```


For an agent with its own key, use the Lys agent options instead.

**Terminal**

```bash
haematite mcp --config /etc/haematite/service.json \
  --lys-agent https://lys.example.au ward-agent --key-file /etc/haematite/agent-key.pem
```


The [permissions page](/haematite/concepts/sign-in-and-permissions/#present-an-agents-pass) also gives credential-file and run-pass forms. Only one pass source can be selected. Key and credential files require absolute paths. A relative `--pass-file` is accepted and resolved from the MCP process’s working directory. `--socket` can replace `--config`; `--timeout-ms` bounds store and issuer requests.

The process reads one JSON-RPC message per line. Standard output carries responses only; diagnostics go to standard error. End of standard input exits 0. A process exit of 0 does not mean every tool succeeded. Read the result's `isError` and its named refusal.

## HTTP

Put an explicit HTTP object in the service file.

**mcp_http in service.json**

```json
{
  "mcp_http": {
    "listen": "127.0.0.1:7742",
    "allowed_origins": [
      "https://studio.example.au"
    ],
    "path": "/mcp",
    "resource_metadata_url": "https://studio.example.au/.well-known/oauth-protected-resource"
  }
}
```


Start the HTTP adapter separately from the store.

**Terminal**

```bash
haematite mcp-http --config /etc/haematite/service.json
```


Keep the listener on loopback and put TLS at the proxy. The public MCP address is the proxy origin plus the configured path. The resource metadata URL belongs to the sign-in arrangement and must be served separately.

Each request needs one bearer pass for haematite. A present browser origin must be allowed. POST requests use JSON content type and accept both JSON and event streams.

**A client request**

```http
POST /mcp HTTP/1.1
Host: studio.example.au
Content-Type: application/json
Accept: application/json, text/event-stream
Authorization: Bearer <pass supplied by the MCP client>

{"jsonrpc":"2.0","id":1,"method":"tools/list"}
```


A missing bearer is HTTP 401 `mcp_sign_in_required`. A refused pass gets a 401 sign-in challenge. A wrong origin is 403 `mcp_http_origin_refused`. An Accept header missing either response type is 406 `mcp_http_accept_invalid`. A request accepted by HTTP can still contain a tool refusal.

For a host that supports a header helper, use the `mcp-pass` command to obtain its `Authorization` object from a key, credential or run pass. Hand standard output directly to the host. Do not paste its result into a configuration file or terminal transcript.

Write one bearer header object for an MCP client header helper. The output contains a pass; give it directly to the client.

```text
haematite mcp-pass --lys-agent <issuer origin> <agent id> ((--credential-file | --key-file) <absolute path> | --run-pass-env <variable>) [--timeout-ms N]
```

- `--timeout-ms N`: Positive milliseconds for the Lys request, default 10000.


## The repository plugins

The repository has a Claude Code marketplace at `.claude-plugin/marketplace.json` and a Codex marketplace at `.agents/plugins/marketplace.json`. Both point to `plugins/haematite`. Add that local marketplace through the host's plugin interface and select haematite. No store data belongs in the plugin directory.

At main `84b9497a`, the Claude Code manifest starts `scripts/mcp.sh` through `CLAUDE_PLUGIN_ROOT`. It requires the `config_file` user setting and passes the optional `lys_issuer`, `lys_agent` and `key_file` settings to the launcher. Set all three agent settings for key-based sign-in, or give the MCP registration another explicit pass source.

At that same commit, the Codex manifest reads `./.mcp.json`, which starts `./scripts/mcp.sh` in the plugin directory. It includes the haematite skill from `./skills/`. The launcher selects the configured pass file first, then the agent's key, credential or run-pass proof in that order.

- `HAEMATITE_CONFIG`: The service configuration path. Defaults to HOME/.config/haematite/serve.json. Missing files are haematite_mcp_config_missing, exit 78.
- `HAEMATITE_BIN`: The executable path or name. Defaults to haematite on PATH. Missing commands are haematite_mcp_binary_missing, exit 127.
- `HAEMATITE_PASS_FILE`: Pass file, used first when set. The launcher passes it as --pass-file.
- `HAEMATITE_LYS_AGENT`: Agent id. Requires HAEMATITE_LYS_ISSUER and a proof. A missing issuer is haematite_mcp_issuer_missing, exit 78.
- `HAEMATITE_LYS_ISSUER`: Lys origin for the agent pass route. No API path.
- `HAEMATITE_KEY_FILE`: Agent key path. First proof selected for HAEMATITE_LYS_AGENT.
- `HAEMATITE_CREDENTIAL_FILE`: Grant credential path. Selected when there is no key path.
- `HAEMATITE_RUN_PASS_ENV`: Name of the variable holding a run pass. Selected when neither file proof is set. LYS_AGENT_PASS is the usual seat variable.
- `HOME and PATH`: Passed to the launcher for its default configuration location and executable lookup. LYS_AGENT_PASS is also forwarded by the repository MCP registration.


An agent without a proof is `haematite_mcp_agent_proof_missing`, exit 78. With no pass source at all, the script prints `haematite_mcp_no_pass` and starts MCP without one. A protected store then refuses tool calls as `pass_missing`; an unprotected private store can answer. The warning is not a successful sign-in.

These plugin details were reread at main `84b9497a`. Host installation remains a separate check.

## An installed bundle

At main `84b9497a`, installation writes the binary, configured workers, `serve.json`, the skill and both plugin manifests into the bundle. It also writes `.mcp.json` and both marketplace files, using the bundle as their local plugin source. Without agent options, the Codex registration names absolute binary and configuration paths and the Claude Code registration uses `CLAUDE_PLUGIN_ROOT`.

Without agent options, those generated MCP registrations select no pass source and the installer prints `bundle_no_pass`. With `--lys-issuer`, `--lys-agent` and `--key-file` together, the preview installer adds `scripts/mcp.sh` and configures both registrations with that agent. It records the canonical key-file path; it does not copy the key. Moving the bundle requires a new installation because generated paths are absolute. See [Install bundles](/haematite/operations/install-bundle/).

The installed binary used to verify these pages produced the older Claude Code bundle layout. The Codex bundle and marketplace additions are preview source after tag `v0.20.1`; no tagged release carrying those additions was verified. The run-pass, header-helper and schema-recording commands are present in the `v0.20.1` source, despite omissions in top-level help.

## Prove the connection

List the tools, then describe the store.

**MCP request**

```json
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
```


**MCP request**

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "describe_store",
    "arguments": {
      "selection": {
        "branch": "main"
      },
      "page": {
        "rows": 100,
        "bytes": 65536
      },
      "after": {}
    }
  }
}
```


All eleven tools were called against the scratch store. The read tools returned captured versions. Writes returned receipts. `review_branch` takes an object for `after`, including an empty object for its first page. The [tool reference](/haematite/reference/mcp-tools/) lists every required field.

Repository manifest inspection and launcher execution do not prove installation into a particular Claude Code or Codex session. Verify the host's tool list and one successful store read after installing.

## A hosted assistant seat

From 0.21.0, `haematite assistant run --config` runs the store's assistant seat: a contained Agent Client Protocol host for one person and one store. It is separate from the MCP adapter. The seat connects to the door's `assistant.socket`, and Studio's panel reaches it through the door ([Use the assistant](/haematite/getting-started/use-the-assistant/)). Lys starts the seat; that start is not yet in the installed Lys.

The seat JSON requires `harness`, `door_socket`, `serve_config`, `key_file`, `issuer` and `store`. The three file or socket paths must be absolute; issuer and store must be nonempty. The harness requires an `id`, `program` and an `args` array. Loading checks `program` only for a non-empty value. Executable path resolution and the executable-file check happen when the host spawns the harness. Its optional `cwd` must be absolute; optional `session` must be nonempty and no longer than `content_bytes`.

The source catalogue recognises `claude-code`, `codex`, `gemini-cli`, `opencode` and `pi`. The current host refuses `pi` as `pi_mcp_unavailable` because that adapter drops MCP servers. A program is an absolute executable path or a bare name resolved only in absolute PATH entries. Arguments are passed without a shell. This catalogue is not proof that a particular harness implements the negotiated protocol.

Every harness bound is a positive platform-sized integer. The write and stream queues must also fit the channel capacity. Defaults are `prompt_queue` 32, `content_bytes` 65536, `acp_frame_bytes` 262144, `pending_permissions` 1024, `write_queue` 2048, `stream_queue` 128, `issuer_response_bytes` 65536, `session_list` 50 and `title_chars` 120. Unknown seat or harness fields are refused. Invalid limits name the harness field.

The seat rejects inherited environment names containing key, token, secret, password, credential or access-key markers, except the explicit `LYS_AGENT_PASS` launch proof. `seat_unreadable`, `seat_invalid`, `seat_paths_must_be_absolute`, `seat_store_and_issuer_required` and `credential_environment_refused` identify declaration failures. Fix the launch declaration at its owner rather than adding credentials to the harness environment.

The current host requires `LYS_AGENT` and its `LYS_AGENT_PASS` launch proof. It exchanges that proof for a store pass, then presents it to its separate owner-only assistant socket. Permission requests become Lys drafts. A matching person decides the exact draft before the host sends a one-time choice to the harness. A malformed request, failed hold or cancellation receives no approval. This behaviour is read from the 0.21.0 source. It has not yet been run with a seat Lys started.

The preview host records a private `.revoke-owed` file beside the seat file before acquiring a pass. Closing asks Lys to revoke the pass. If revocation fails, the file remains and the next launch must settle it before proceeding. This file contains the launch proof and must retain its owner-only mode. This lifecycle is read from the 0.21.0 source and has not yet been run with a seat Lys started.
