io.github.smlfg/hai-mcp·DevOps

HAI-MCP

Model-agnostic MCP control plane for HAI: bounded missions, owner-gated actions, drift checks.

Stars
0
Version
0.1.0
License
MIT
Review
Passed
Language
Python
Last commit
2h ago
Forks
0
Open issues
6
Registry update
Oct 13, 2026
agent-controlagentic-aiai-agentshuman-agent-interfacemcp-servermodel-context-protocolowner-gates

Connect

pypi
Terminal
$claude mcp add hai-mcp \  -e HAI_HOME=<hai-home> \  -e HAI_OWNER_HOME=<hai-owner-home> \  -e HAI_TRANSPORT=<hai-transport> \  -e HAI_HTTP_HOST=<hai-http-host> \  -e HAI_HTTP_PORT=<hai-http-port> \  -e HAI_HTTP_TOKEN=<hai-http-token> \  -- uvx hai-mcp==0.1.0

Environment variables

  • HAI_HOME

    Directory for the global HAI state (missions, leases, proofs). Default: ~/.hai

  • HAI_OWNER_HOME

    Directory for the owner out-of-band channel (one-time nonces). Deliberately outside HAI_HOME. Default: ~/.hai-owner

  • HAI_TRANSPORT

    Transport selection: 'stdio' (default) or 'streamable-http'.

  • HAI_HTTP_HOST

    Host for streamable-http transport. Refused unless loopback or HAI_HTTP_TOKEN is set.

  • HAI_HTTP_PORT

    Port for streamable-http transport.

  • HAI_HTTP_TOKENsecret

    Bearer token required to accept non-loopback streamable-http connections. Loopback binds without a token.

Security review

Review passed

Reviewed 44m ago.

metadata
scanned
packages
1 checked

No findings.

Tools

This server runs on your machine, so its tool list is not fetched before install — the README below describes what it offers.

HAI-MCP banner

HAI-MCP · Model-agnostic MCP control-plane for the Human Agent Interface (HAI) approach

License: MIT Python ≥ 3.12 MCP Issues


Why this repo exists

Agents are now driving shells, editors, browsers, and CI. Most of them work without a human in the loop long enough for the human to lose the thread. HAI — the Human Agent Interface — is an opinionated, evidence-based way to keep agentic work observable, bounded, owner-gated, and recoverable so a human can still own the work even when an agent is doing it.

HAI-MCP is the open-source, model-agnostic implementation of that approach. It is one of the pieces of Samuel Fleig's HAI stack — the part that any Model-Context-Protocol-speaking client can talk to, regardless of which LLM sits behind it.

Canonical site: https://www.human-agent-interface.com/ · About Samuel: https://www.human-agent-interface.com/samuel/


What "MCP control-plane" means here

MCP — the Model Context Protocol — is a standard JSON-RPC interface that lets a tool server expose tools, resources, and prompts to an LLM-backed client (Claude Code, Codex, Cursor, Grok, OpenCode, Hermes, …). The client discovers the server's tools and calls them during a conversation; the server stays stateless from the model's point of view.

A control-plane server is one that does not do the work itself — it governs how work is authorised, scoped, recorded, and handed back to the human. Concretely, an MCP control-plane:

ConcernWhat HAI-MCP exposes for it
Mission contracthai_open_mission, hai_get_contract, hai_recontract
Session leasinghai_authorize_session — time-bounded, contract-versioned
Activity classificationhai_check_activity / hai_drift_check — on-contract vs. off-contract
Focus & laneshai_set_focus, hai_status — at most 2 ACTIVE lanes
Owner gateone-time nonces out-of-band (HAI_OWNER_HOME or ntfy) for accept / recontract / abandon
Evidence & closurehai_proof, hai_close_mission, hai_stop
Inbox & intakehai_intake, hai_distill, hai_park, hai_park_item
Recoveryhai_checkpoint, hai_recover

HAI-MCP is deliberately not an agent runtime. It never calls an LLM, never runs shell, never pushes to git, and never deletes files. It is the judge — agents call it to ask "may I?" and to record "I did".


Features

  • One state engine, 23 tools — three surfaces (legacy control plane, mission lifecycle, daily-loop wrappers) sharing one hardened core.
  • Strict fail-closed boundary — inputs are typed and rejected, not coerced. A String "2" is refused for an Integer field; the server stays reachable instead of silently mangling types.
  • Versioned mission contracts — every mission has a contract with a monotonic version. Recontracting produces a visible field-level diff and revokes every existing lease.
  • Time-bounded session leases — hai_authorize_session binds a lease to a mission id, a contract version, a duration, a role, a contribution, and a claimed criterion set.
  • Deterministic drift check — hai_check_activity classifies planned or observed activity against the contract without ever calling an LLM.
  • Two-principal owner gate:
    • Default (nonce): the server delivers a one-time code to the owner channel (HAI_OWNER_HOME/file or ntfy). The client never sees the code. The code is bound to the exact change, single-use, expiring; only the hash is stored in HAI_HOME.
    • ack_legacy: the old self-asserted owner_ack flag — an honor system, surfaced as such by hai_health.
  • Mount table — hai_bind_project joins a logical project_id to a device-local directory behind owner_ack + reason.
  • Model-agnostic — same binary, same tools, same contract. Switch LLMs without rewriting governance.
  • Two transports — stdio (default) and streamable-http with optional bearer-token binding (loopback only by default).
  • Checkpoint & recover — hai_checkpoint snapshots context; hai_recover returns the smallest recovery next action.
  • 139 tests green — the engine is covered; the slice remains open for the learning-budget and owner-instrument follow-ups.

Install & run

Requires Python ≥ 3.12 and uv.

git clone https://github.com/smlfg/hai-mcp.git
cd hai-mcp
uv sync --all-extras
uv run hai-mcp          # stdio transport, default

Streamable-HTTP transport:

HAI_TRANSPORT=streamable-http \
HAI_HTTP_HOST=127.0.0.1 \
HAI_HTTP_PORT=8765 \
HAI_HTTP_TOKEN="$(openssl rand -hex 32)" \
uv run hai-mcp

HTTP binding to non-loopback hosts is refused unless HAI_HTTP_TOKEN is set. See docs/OWNER_GATE.md and the --help output.


Sample usage — wiring the server into a client

The server is transport-agnostic; every MCP client gets the same 23 tools. Drop the snippet into the client's MCP config and restart.

Claude Code / Claude Desktop

{
  "mcpServers": {
    "hai": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/hai-mcp",
        "hai-mcp"
      ],
      "env": {
        "HAI_HOME": "/home/you/.hai",
        "HAI_OWNER_HOME": "/home/you/.hai-owner"
      }
    }
  }
}

Cursor

{
  "mcpServers": {
    "hai": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/hai-mcp",
        "hai-mcp"
      ],
      "env": {
        "HAI_HOME": "/home/you/.hai",
        "HAI_OWNER_HOME": "/home/you/.hai-owner"
      }
    }
  }
}

Codex / Hermes / any MCP stdio client

Same shape — point at the uv run --directory … hai-mcp command and pass the two env vars. More snippets live in docs/client-snippets/.


Sample usage — driving the protocol

A typical agent loop calls HAI-MCP before it acts and after it acts.

1. Open a bounded mission

hai_mission_start(
  problem   = "Refactor the evaluator harness to be deterministic.",
  artifact  = "Pull request titled 'evaluator: deterministic harness'.",
  done_criteria = [
    {"id": "C1", "description": "Two A/B cases produce identical verdicts on three reruns."},
    {"id": "C2", "description": "Harness runs offline; no live network calls."},
  ],
  owner             = "samuel",
  time_limit_hours  = 6,
  non_goals         = ["Do not touch the MCP server.", "Do not change contract shapes."],
  constraints       = {"python": ">=3.12", "branch": "slice/eval-determinism"},
)

2. Authorise a session lease

hai_authorize_session(
  mission_id         = "<from open>",
  contract_version   = 1,
  agent_identity     = "codex-cli",
  role               = "implementer",
  contribution       = "Deterministic A/B harness",
  expected_result    = "Green pytest with frozen inputs.",
  duration_minutes   = 90,
  criterion_ids      = ["C1", "C2"],
  device_id          = "mbp-mirgraatin",
  harness_id         = "codex-cli/0.45",
)

3. Check an activity before running it

hai_check_activity(
  session_id     = "<from lease>",
  activity_step  = "Replace random.seed with a seeded fixture in tests/test_eval_ab.py",
  criterion_id   = "C1",
  affected_paths = ["tests/test_eval_ab.py", "tests/fixtures/ab/case1.json"],
)
# → {"verdict": "on_contract", ...}

4. Close with evidence

hai_proof(
  mission_id        = "<from open>",
  contract_version  = 1,
  outcome_summary   = "A/B cases produce identical verdicts on three reruns; harness offline.",
  evidence          = {
    "C1": {"command": "pytest tests/test_eval_ab.py -q", "exit_code": 0,
           "reruns": [0, 0, 0]},
    "C2": {"command": "pytest tests/test_eval_ab.py -q --offline", "exit_code": 0},
  },
)

5. Owner-gated promotion

For hai_accept_next_step, hai_recontract, or abandoning a mission, the first call without owner_code delivers a one-time code to the owner's channel (the file sink in HAI_OWNER_HOME, or an ntfy topic). Relaying that code back as owner_code=<code> completes the action. The code is bound to the exact change, single-use, and only its hash is persisted in HAI_HOME. Detail: docs/OWNER_GATE.md.


State layout

  • Global: $HAI_HOME — default ~/.hai
  • Owner channel: $HAI_OWNER_HOME — default ~/.hai-owner (deliberately outside HAI_HOME so the agent cannot read it)
  • Per-project: <project_path>/Projek-Managment/ (legacy) or device mount + relative prefixes when a project_id is bound
  • Mount table: HAI_HOME/core/projects.json

All writes are resolved and confined to these roots.


Project layout

hai-mcp/
├── docs/
│   ├── TOOL_CONTRACT.md      # normative tool contract (gates, errors, schemas)
│   ├── OWNER_GATE.md         # nonce + ack_legacy owner-gate spec
│   ├── BUILD_HANDOFF.md
│   ├── client-snippets/      # ready-to-paste mcp.json snippets
│   ├── eval/                 # A/B harness contract
│   ├── plans/                # dated design slices
│   └── visuals/              # mission-lifecycle banner assets
├── scripts/                  # stdio smoke + impact-eval runner
├── src/hai_mcp/              # the server
└── tests/                    # 139 tests

Limitations

HAI-MCP is intentionally narrow. It does not:

  • Call any LLM. The server is dumb on purpose — governance is a separate concern from generation.
  • Execute code, run shells, push git, or delete files. It refuses tools like "commit & push", "run pytest", "rm -rf". Agents are expected to do those things and report back via hai_check_activity / hai_proof.
  • Merge with hai-intake packages. The intake pipeline is a separate stack; coexistence is fine, fusion is not.
  • Provide a UI. There is no dashboard. The control plane speaks MCP and writes JSON; visualisation is the client's job.
  • Multi-mission concurrency. Exactly one active mission globally. Open a new one only after the previous is closed (completed or abandoned).
  • Cross-device writes. Each device sees only its own mounts. The mount table is per-device; the contract is global.
  • Forward compatibility for major MCP bumps. The server pins mcp>=1.9.0,<2; a v2 of MCP will require a separate migration slice.
  • No revocation list / no kill-switch API. Lease invalidation happens via recontract, mission close, or hai_stop; there is no out-of-band "revoke everything right now" tool.
  • No automatic recovery from a corrupted HAI_HOME. If the global state directory is corrupted, the server fails closed — by design, but recovery is manual (see hai_recover).

See docs/TOOL_CONTRACT.md for the full gate matrix and docs/OWNER_GATE.md for the owner-gate contract.


Tests

uv run pytest                       # full suite
uv run python scripts/stdio_smoke.py  # stdio round-trip smoke

The repo carries 139 green tests as of the 0.1.0 slice. CI status lives on the GitHub Actions page of the repository.


Contributing

The control plane is small on purpose — additions go through a contract slice:

  1. Read docs/TOOL_CONTRACT.md.
  2. Open an issue describing the tool / gate / state change.
  3. Land it behind a fail-closed slice with tests + plan in docs/plans/.

Pull requests that change gates, schemas, or the mount table without a matching plan will be asked to follow the same path.


License

MIT — see LICENSE. Copyright © 2026 Samuel Fleig.


Acknowledgements

HAI-MCP is one piece of Samuel Fleig's HAI stack. It stands on:

  • the Model Context Protocol specification,
  • the mcp Python SDK (mcp>=1.9.0,<2),
  • and the legacy ~/.config/hai-agent-mcp Hermes bridge that this server replaces for control-plane work. Coexistence is supported; the bridge is not required.

Canonical HAI site: https://www.human-agent-interface.com/ · About Samuel Fleig: https://www.human-agent-interface.com/samuel/

From the server's repository — third-party content, read it before running anything it suggests.

More in DevOps

Listing from the official MCP Registry · repository facts from GitHub, 14m ago.