> For the complete documentation index, see [llms.txt](https://docs.zetrix.com/zetrix-l2-documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zetrix.com/zetrix-l2-documentation/integration/zetrix-mcp.md).

# Z2 Zetrix MCP

> **Repository:** [`z2-zetrix-mcp`](https://github.com/Zetrix-Chain/z2-zetrix-mcp) (GitHub) · **npm:** [`z2-zetrix-mcp`](https://www.npmjs.com/package/z2-zetrix-mcp) (v0.1.5) · full design docs live in the repo's [`docs/`](https://github.com/Zetrix-Chain/z2-zetrix-mcp/tree/main/docs) (00 overview → 08 deployment).

## Purpose

**Zetrix MCP** (`z2-zetrix-mcp`) is a Model Context Protocol server for the Z2 rollup. It exposes the L1 (Zetrix) and L2 (Besu EVM) sides and the bridge between them as **typed tools**, so an agent (Claude Desktop / Claude Code, Cursor, …) can **deposit**, **withdraw**, and **read L2 state** for native ZETRIX, ZTP-20, and ZTP-721 without hand-crafting node calls, leaf hashes, or confirmation polling.

It is **intent-driven** (e.g. *"withdraw 5 ZETRIX to ZTX3…"* resolves the asset, burn, confirmation wait, and claim), **read-only by default**, and **holds no custody** — keys are passed per call (or via env), never stored, never logged.

## Features

| Feature             | Description                                              |
| ------------------- | -------------------------------------------------------- |
| Deposit             | Initiate L1→L2 deposits (native ZETRIX, ZTP-20, ZTP-721) |
| Withdraw            | Start & claim L2→L1 withdrawals                          |
| NFT support         | ZTP-721 bridging and queries                             |
| Transaction status  | Track L1 tx / L2 receipt / withdrawal status             |
| L2 information APIs | Bridge state, balances, token info, L2 calls             |

## Prerequisites & Installation

* **Node.js ≥ 20** (npm package `z2-zetrix-mcp`, current version `0.1.5`; binary `z2-zetrix-mcp`).

```bash
npx z2-zetrix-mcp            # run on demand (no install)
npm install -g z2-zetrix-mcp # or install globally
```

From source:

```bash
git clone https://github.com/Zetrix-Chain/z2-zetrix-mcp.git
cd z2-zetrix-mcp && npm install && npm run build
```

## Configuration

The server speaks MCP over **stdio** and is configured entirely through **environment variables**. It runs **read-only by default**; signing tools are registered only when `Z2_ALLOW_WRITES=true`.

### Zero-config on testnet

You do **not** need to configure anything to use testnet. The package ships a **bundled profile** with the current testnet addresses and endpoints, so `npx z2-zetrix-mcp` runs read-only out of the box — no profile file, no endpoint URLs, no contract addresses to look up. You only add config to **enable writes** (a signing key) or to **target a different deployment** (e.g. mainnet).

### Deployment profile (`Z2_PROFILE`)

A profile JSON holds a deployment's contract addresses (L1 `Bridge`/`Outbox`/`Rollup`, L2 `L2Bridge`/`WithdrawalManager`/`StateReceiver`/`wZETRIX`), endpoints, and `chainId`. `Z2_PROFILE` defaults to the bundled `profiles/testnet.example.json`; point it at your own file (absolute path) to target another deployment.

> \[!NOTE] At startup the server **validates the profile against the live chain** (Bridge wiring + L2Bridge getters + chainId) and **refuses to start on a mismatch** — so it can never act against a stale or wrong deployment. Regenerate the profile from the contract repo's `deployed-addresses-l1-<date>.json` after any redeploy.

### Environment variables

| Variable                | Default                           | Purpose                                               |
| ----------------------- | --------------------------------- | ----------------------------------------------------- |
| `Z2_L1_NODE_URL`        | `test-node.zetrix.com`            | Zetrix L1 node host (no scheme)                       |
| `Z2_L2_RPC_URL`         | `https://z2-test-node.zetrix.com` | Besu L2 JSON-RPC URL                                  |
| `Z2_PROFILE`            | bundled `testnet.example.json`    | Deployment profile JSON path                          |
| `Z2_L2_CHAIN_ID`        | `938748`                          | L2 chain ID (current Testnet)                         |
| `Z2_L1_SECURE`          | `true`                            | Use HTTPS to the L1 node                              |
| `Z2_ALLOW_WRITES`       | `false`                           | Register the signing/write tools                      |
| `Z2_L1_SIGNER_KEY`      | –                                 | L1 signer key fallback (deposits, `map_token`, claim) |
| `Z2_L2_SIGNER_KEY`      | –                                 | L2 signer key fallback (the withdrawal burn)          |
| `Z2_CONFIRM_TIMEOUT_MS` | `900000`                          | Max wait for assertion confirmation (withdraw)        |
| `Z2_POLL_INTERVAL_MS`   | `45000`                           | Confirmation/relay poll cadence                       |
| `Z2_LOG_LEVEL`          | `info`                            | Log level (stderr; keys always redacted)              |

All variables are **optional on testnet** (the bundled profile covers them). Provide keys only for what you do: the **L1 key** signs deposits/claims; the **L2 key** signs the withdrawal burn.

> \[!IMPORTANT] Set `Z2_L2_CHAIN_ID` to your target network's chain ID — **`938748`** for the current Testnet (see [Wallet Configuration](/zetrix-l2-documentation/usage/wallet-configuration.md#wallet-configuration)) — and verify it with an `eth_chainId` call against `Z2_L2_RPC_URL`.

> \[!WARNING] `Z2_L1_SIGNER_KEY` / `Z2_L2_SIGNER_KEY` are **private keys**. Prefer passing a `signer` **per tool call** over env keys (an env key is inherited by child processes and readable via `/proc/<pid>/environ`); a key placed in a **client config file** is stored **at rest on disk**. Never commit keys to source control or the docs repo. Keep `Z2_ALLOW_WRITES=false` for read-only agents; for a shared HTTP-hosted instance, keep it read-only unless a scoped signer is provisioned behind TLS + auth.

### MCP client configuration

**Claude Code** (CLI):

```bash
# Read-only (testnet)
claude mcp add z2-zetrix-mcp -- npx -y z2-zetrix-mcp

# With write access
claude mcp add z2-zetrix-mcp -e Z2_ALLOW_WRITES=true -e Z2_L1_SIGNER_KEY=<priv> -- npx -y z2-zetrix-mcp
```

**Claude Desktop / Cursor** (`mcpServers` block in the client's config JSON). Testnet read-only needs **no `env` block** — add one only to enable writes or target another deployment:

```jsonc
{
  "mcpServers": {
    "z2-zetrix-mcp": {
      "command": "npx",
      "args": ["-y", "z2-zetrix-mcp"],
      "env": {
        "Z2_L2_RPC_URL": "https://z2-test-node.zetrix.com",
        "Z2_L2_CHAIN_ID": "938748",
        "Z2_ALLOW_WRITES": "true",
        "Z2_L1_SIGNER_KEY": "<priv>",   // Zetrix (L1) key — deposits, map_token, claim
        "Z2_L2_SIGNER_KEY": "0x<priv>"  // EVM (L2) key — the withdrawal burn (omit if deposit-only)
      }
    }
  }
}
```

**Command variants:** installed globally, use `"command": "z2-zetrix-mcp", "args": []` (or keep `npx`, which also finds the global bin). From source, use `"command": "node", "args": ["/abs/path/dist/index.js"]`. To target **mainnet/another deployment**, also set `Z2_PROFILE` (and optionally `Z2_L1_NODE_URL` / `Z2_L2_RPC_URL`).

## Tools Exposed

| Category                                   | Tools                                                                                                                          |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| **Utility**                                | `create_l2_account`, `derive_l2_address`, `compute_leaf_hash`                                                                  |
| **Read / Query**                           | `bridge_state`, `l2_balance`, `token_info`, `withdrawal_status`, `l1_query`, `l2_call`, `get_l1_transaction`, `get_l2_receipt` |
| **Write** (require `Z2_ALLOW_WRITES=true`) | `deposit`, `withdraw`, `start_withdrawal`, `claim_withdrawal`, `map_token`, `l1_invoke`, `l2_send`                             |

All write tools support **`dryRun: true`** — build + fee-estimate (and, for a claim, run the full leaf verification) and return what *would* be submitted, **without** sending. Full schemas and examples: repo [`docs/02-tools-reference.md`](https://github.com/Zetrix-Chain/z2-zetrix-mcp/blob/main/docs/02-tools-reference.md).

## Usage — Deposit (L1 → L2)

No L2 address yet? Ask the agent to *"create an L2 account"* → `create_l2_account` returns a fresh `0x…` address + private key. Deposit to that address (you only need the **address** to receive; the key is needed later to withdraw). L1 and L2 are separate keypairs — the L1 `ZTX3…` account signs the deposit; the L2 `0x…` account just receives.

| You say                                            | The agent calls                                                                            |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| *"Deposit 1 ZETRIX to my L2 address `0x6459…`"*    | `deposit { asset: "native", amount: "1000000", l2Recipient: "0x6459…" }`                   |
| *"Deposit 100000 DUSD (`ZTX3Tcct…`) to `0x6459…`"* | `deposit { asset: "ztp20", token: "ZTX3Tcct…", amount: "100000", l2Recipient: "0x6459…" }` |
| *"Bridge NFT #3 of `ZTX3b3Wh…` to `0x6459…`"*      | `deposit { asset: "ztp721", token: "ZTX3b3Wh…", tokenId: "3", l2Recipient: "0x6459…" }`    |

Amounts are in base units (1 ZETRIX = 1,000,000 ZETA). The tool submits the L1 deposit (auto-approving the Bridge for tokens) and returns the L1 tx hash; the **relayer mints on L2 automatically after \~3 L1 blocks**. On L2 the asset arrives as a **token contract**, not the gas coin: native → **wZETRIX** ERC-20, ZTP-20 → ERC-20 child, ZTP-721 → ERC-721 child. Confirm with `l2_balance { holder, asset: "native" }` (native resolves wZETRIX automatically).

## Usage — Withdraw (L2 → L1)

Withdrawal is inherently multi-step (burn on L2 → wait for the optimistic-rollup confirmation window → claim on L1). The **`withdraw`** tool does all of it in one call.

| You say                                              | The agent calls                                                                              |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| *"Withdraw 0.5 ZETRIX to `ZTX3WJqe…`"*               | `withdraw { asset: "native", amount: "500000", l1Recipient: "ZTX3WJqe…" }`                   |
| *"Withdraw 50000 DUSD (`ZTX3Tcct…`) to `ZTX3WJqe…`"* | `withdraw { asset: "ztp20", token: "ZTX3Tcct…", amount: "50000", l1Recipient: "ZTX3WJqe…" }` |
| *"Withdraw NFT #3 (`ZTX3b3Wh…`) to `ZTX3WJqe…`"*     | `withdraw { asset: "ztp721", token: "ZTX3b3Wh…", tokenId: "3", l1Recipient: "ZTX3WJqe…" }`   |

It needs an **L2 signer** (owns the L2 asset — signs the burn) and an **L1 signer** (pays the claim), via `l2Signer`/`l1Signer` in the call or `Z2_L2_SIGNER_KEY`/`Z2_L1_SIGNER_KEY`. It burns, polls until the covering assertion is **CONFIRMED** (up to `Z2_CONFIRM_TIMEOUT_MS`), verifies the leaf against the on-chain send root, then claims — returning `{ l2WithdrawTx, l1ClaimTx, releasedTo }`. If confirmation outlasts the timeout it returns a **resumable handle**; finish later with `claim_withdrawal`. For manual control use `start_withdrawal` → poll `withdrawal_status` until `claimable: true` → `claim_withdrawal`.

> \[!NOTE] **Multi-withdrawal assertions.** When several withdrawals share one assertion window, the send root is a Merkle tree over all of them. The server rebuilds the tree automatically, but needs the **original ZTX3 token of each&#x20;*****other*****&#x20;non-native exit** — pass them as `windowTokens` on the claim (native siblings need none). A sibling whose token isn't supplied **fails closed** rather than producing a wrong proof.

## Intended Use Cases

* AI agents interacting with Z2 (via MCP-capable clients).
* CLI/automation for deposits, withdrawals, and status checks.
* Application backends integrating Z2 without hand-rolling RPC/bridge logic.

## Inputs / Outputs

| Inputs         | Outputs                                          |
| -------------- | ------------------------------------------------ |
| MCP tool calls | Z2 RPC / bridge operations, structured responses |

## Dependencies

* Z2 RPC endpoint; bridge/relayer; Node.js runtime.

## Deployment

* Installed as a global npm package; run as an MCP server process.

## Security & Safety Model

* **Read-only by default** — write tools aren't registered unless `Z2_ALLOW_WRITES=true`.
* **Fail-closed claims** — `claim`/`withdraw` submit only if the assertion is CONFIRMED **and** the computed Merkle root equals the on-chain `getSendRoot`; otherwise they raise (`AssertionNotConfirmed` / `LeafMismatch` / `RootTokenUnresolved`) and send nothing.
* **`dryRun`** on every write tool for preview-before-sign.
* **Deposit approve-gate** — a token deposit is not attempted unless its auto-approve is confirmed.
* **`UNKNOWN` ≠ success** — an L1 write whose outcome isn't observed returns `status: "UNKNOWN"` (never a false success); poll the hash.
* **Keys** — never persisted or logged; any signer appearing in an error is redacted. Prefer per-call `signer` over env/config-file keys.
* **Profile validation** — startup aborts if the profile doesn't match live on-chain wiring.
* Treat MCP as a privileged automation surface — restrict who can invoke it. See repo [`docs/07-security.md`](https://github.com/Zetrix-Chain/z2-zetrix-mcp/blob/main/docs/07-security.md).

## Failure Scenarios & Recovery

| Failure         | Effect          | Recovery                                  |
| --------------- | --------------- | ----------------------------------------- |
| RPC unreachable | Operations fail | Retry; point at healthy RPC/LB            |
| Version drift   | API mismatch    | Pin package version; upgrade deliberately |

## Best Practices

* Pin the package version; validate against the target network.
* Use least-privilege credentials.

***

[← System Components](/zetrix-l2-documentation/architecture/system-components.md) · [Index](/zetrix-l2-documentation/readme.md) · [Network Endpoints →](/zetrix-l2-documentation/network-and-deployment/network-endpoints.md)
