← Back to dashboard
hazina-mcptoolingfreshReader view (for NotebookLM)

Hazina MCP Integration Guide

What is the Hazina MCP?

The real model

McpServer + registerTool over StdioServerTransport, bridged into Windows by wsl.exe.

Built on @modelcontextprotocol/sdk 1.29 with Zod input schemas; stdout is reserved for the protocol so every log goes to stderr, and one stray console.log corrupts the stream. The vault is the Linux source of truth at ~/.codeamani/hazina, and its master key is DPAPI-wrapped — the Linux launcher shells back out to Windows powershell.exe over WSL interop to unwrap it, so key custody stays with the Windows account. Windows clients (Cursor's Grok bot, Claude Code desktop) reach it via wsl.exe -d Ubuntu -- /home/barnabas/.local/bin/hazina-mcp. Do not add an env block to that config: it applies to the Windows process and only crosses into Linux via WSLENV, so a HAZINA_HOME set there is silently ignored and you quietly bind against the wrong vault. For codeAmani this is the difference between convenience and exposure — Daraja consumer secrets and Stripe live keys are the crown jewels, and an M-Pesa credential in a summarized transcript is real money.

Five Hazina MCP primitives

Transport, trust boundary, and the bridge that joins two operating systems.

Hazina MCP Integration Guide

Focus — running codeAmani's first-party Hazina MCP server from WSL and exposing it to Windows-side agents (Cursor's Grok bot, Claude Code desktop) without ever moving the vault.

Overview

Most MCP servers in this stack are vendor-hosted and remote — you point a URL at them and attach a bearer token. Hazina is the opposite: a local stdio server you own, launched as a child process, speaking JSON-RPC over stdin/stdout. There is no network listener and no endpoint to leak.

It fronts the Hazina encrypted secrets vault. The defining design decision is that no MCP tool returns a secret value. Agents get names, references, wiring graphs and readiness reports; the only path a real value takes is inject, which writes a gitignored env file on disk that the agent never reads back. That is what makes it safe to hand an autonomous agent.

Source of truth is Linux. The vault lives at ~/.codeamani/hazina inside WSL. The Windows copy under C:\Users\info\.codeamani\hazina is archive-only — do not create new bindings against it.

Official Documentation

Hazina's own repo is private (codeAmani-Labs/hazina-mcp — the MCP layer split out from the vault package); its docs ship in-tree at docs/INSTALL.md and docs/UBUNTU-GLOBAL.md.

How it is built

Hazina uses the standard MCP TypeScript SDK — McpServer + registerTool with Zod input schemas, connected over StdioServerTransport:

Bash
npm install @modelcontextprotocol/sdk zod
TypeScript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new McpServer({ name: 'hazina', version: VERSION });

server.registerTool(
  'status',
  { description: 'Whether the vault is initialized…', inputSchema: {} },
  async () => ({ content: [{ type: 'text', text: summary }] }),
);

await server.connect(new StdioServerTransport());

Two rules follow from stdio transport and are non-negotiable:

  • stdout is the protocol channel. All logging goes to stderr. A stray console.log corrupts the JSON-RPC stream and the client drops the server.
  • The client owns the lifecycle. It spawns the process; there is no port, no daemon.

Defense in depth — the value-free guard. Every tool result passes through a guarded() wrapper before it leaves the process: it re-loads the catalog and asserts (assertNoValues) that no secret value appears as a substring of the serialized payload, throwing rather than returning if one does. So even a future buggy handler cannot leak a value across the agent boundary. To avoid false rejections on short non-secret literals (a PORT, a public URL fragment), the assertion only fires on catalog values at or above MIN_ASSERTED_SECRET_LEN = 16 characters. That constant must stay in sync between the vault package and the split-out codeAmani-Labs/hazina-mcp MCP layer — a length one side treats as "too short to be a secret" the other must treat identically.

Install (WSL)

Bash
cd ~/projects/hazina
npm install
npm run install:global      # -> ~/.local/bin/hazina  and  ~/.local/bin/hazina-mcp
hazina doctor               # readiness: vault, key, DPAPI path, counts (names only)

The hazina-mcp launcher is a bash wrapper that resolves its own symlinks, then:

  • exports HAZINA_HOME defaulting to $HOME/.codeamani/hazina
  • prepends the Windows PowerShell directory to PATH if powershell.exe is not already reachable — needed because the master key is DPAPI-wrapped and unwrapping shells out to Windows from inside WSL
  • execs Node with the tsx loader against src/mcp/server.ts

Because the launcher supplies its own HAZINA_HOME, client configs do not need an env block — see the WSLENV note under Gotchas.

The 14 tools

All are value-free. Verified live against hazina 0.3.0.

ToolWhat it returns
statusVault initialized? entry + bound-project counts
doctorReadiness: HAZINA_HOME, vault/key presence, PowerShell DPAPI path
list_refsEvery secret reference — path, type, tags, field names
list_projectsProject names that have a binding
get_bindingA project's env-to-ref mappings (literals shown as literals)
describe_projectEach env var, its reference, and status (ok/missing/stale)
wiring_summaryFleet-wide graph of env-to-vault refs grouped by namespace
propose_bindingScans .env.example/.env.local, proposes a binding
bindCreate/update a binding: ENV name to vault reference
unbindRemove one ENV name from a binding (does not delete the secret)
injectResolve a binding, write the gitignored env file with real values
auditCross-check catalog vs bindings: stale/unused entries
import_from_os_envImport from OS environment (Windows User/Machine/Process)
pushResolve a binding and push values to Vercel or Netlify

There is no reveal / get_value tool. By design.

inject and push are the only tools that move plaintext, and both write it outward (to a gitignored file, or to a host's env store) rather than returning it into the transcript.

Wiring it to clients

Where the client runs decides whether you need the bridge.

ClientRuns onConfig fileCommand
Grok Build (CLI)inside WSL~/.grok/config.toml/home/barnabas/.local/bin/hazina-mcp
Claude Code (in WSL)inside WSL~/.claude.json/home/barnabas/.local/bin/hazina-mcp
Cursor / Grok botWindows~/.cursor/mcp.jsonwsl.exe bridge
Claude Code desktopWindows~/.claude.jsonwsl.exe bridge

Windows clients — the wsl.exe bridge

JSON
{
  "mcpServers": {
    "hazina": {
      "command": "wsl.exe",
      "args": ["-d", "Ubuntu", "--", "/home/barnabas/.local/bin/hazina-mcp"]
    }
  }
}

wsl.exe transparently proxies stdin/stdout, so the JSON-RPC stream survives the boundary untouched. The same block works in ~/.cursor/mcp.json and in ~/.claude.json.

WSL-native clients

TOML
# ~/.grok/config.toml
[mcp_servers.hazina]
command = "/home/barnabas/.local/bin/hazina-mcp"
Bash
claude mcp add --scope user hazina -- /home/barnabas/.local/bin/hazina-mcp

Verifying the bridge

Do not trust the config — speak the protocol to it:

Bash
printf '%s\n' \
 '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
 '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
 '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
 | wsl.exe -d Ubuntu -- /home/barnabas/.local/bin/hazina-mcp

Expect "serverInfo":{"name":"hazina","version":"0.3.0"} followed by 14 tools.

Gotchas

  • env does not cross the WSL boundary. An env block in mcp.json sets variables on the Windows wsl.exe process. Linux only inherits what WSLENV forwards, so a HAZINA_HOME set there is silently ignored. Rely on the launcher default, or be explicit: wsl.exe -d Ubuntu -- env HAZINA_HOME=/home/barnabas/.codeamani/hazina /home/barnabas/.local/bin/hazina-mcp
  • Git Bash rewrites Linux paths. Testing from Git Bash turns /home/... into C:/Program Files/Git/home/.... Prefix with MSYS_NO_PATHCONV=1. PowerShell and the agents themselves are unaffected.
  • DPAPI needs Windows reachable. The Linux vault's master key is DPAPI-wrapped; unwrapping calls powershell.exe via WSL interop. If interop is disabled, hazina doctor fails.
  • Distro name is load-bearing. -d Ubuntu must match wsl.exe -l -q exactly.
  • Never log to stdout in an stdio server — it corrupts the protocol stream.

Hazina vs the vendor MCPs

HazinaCloudflare / Vercel / xAI
Transportlocal stdio (child process)remote HTTP
Config keycommand + argsurl + Authorization header
Authfilesystem + DPAPI-wrapped keybearer API token / OAuth
Ownershipfirst-party, privatevendor-hosted
Failure modeprocess will not spawn401 / network
Secrets exposurenone — value-free toolstoken sits in the config

Cloudflare's are already wired in ~/.cursor/mcp.json (mcp.cloudflare.com/mcp, docs.mcp.cloudflare.com/mcp, bindings.mcp.cloudflare.com/mcp). Note the contrast: those configs carry ${CLOUDFLARE_API_TOKEN} in a header — exactly the kind of sprawl Hazina exists to eliminate. See cloudflare/, vercel/ and xai/ for the vendor-side guides.

codeAmani notes

  • Zero-exposure is the product. Never add a tool that returns a secret value. If an agent needs a value, it needs inject writing a gitignored file — not a tool result in a transcript that gets logged, summarized and cached.
  • One vault, Linux SoT. ~/.codeamani/hazina in WSL. The Windows path is archived; new bindings against it will drift.
  • Secrets stay server-side. Hazina is local-only — never deploy the MCP server to Vercel or expose it over HTTP. It has no authentication layer because it never needed one.
  • Audit before shipping. audit catches stale refs; wiring_summary shows which project pulls which namespace. Run both before a release touches env config.
  • Vault data lives outside the repo and audit.log records tool calls — treat it as sensitive-but-value-free, and keep it out of git.