Read-first EVM forensics MCP server with RPC discovery and an optional gas-gated signed-transaction relay.
  • Go 99.1%
  • Shell 0.5%
  • Makefile 0.3%
  • Dockerfile 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
dev 4a8b0fafc9
Some checks failed
release / linux-amd64 (push) Failing after 1m8s
qualification / windows-amd64 (push) Successful in 6m17s
qualification / darwin-amd64 (push) Successful in 6m58s
qualification / linux-amd64 (push) Failing after 1m8s
release / darwin-amd64 (push) Successful in 3m52s
release / windows-amd64 (push) Successful in 5m8s
release / release (push) Has been skipped
Report broadcasts below the current base fee
broadcast_transaction now adds a feeWarning when the signed fee ceiling is
below the current or next base fee, since such a transaction cannot be
included yet and holds the sender nonce. requireFeeCoverage refuses the
relay instead, including when the base fee cannot be verified.
2026-10-23 18:38:06 -04:00
.agents/skills/icu-evm-mcp Add signed release pipeline 2026-09-14 23:43:13 -04:00
.forgejo/workflows Report broadcasts below the current base fee 2026-10-23 18:38:06 -04:00
cmd Report broadcasts below the current base fee 2026-10-23 18:38:06 -04:00
docs Bound cache and add resumable receipt aggregation 2026-09-18 18:11:33 -04:00
internal Report broadcasts below the current base fee 2026-10-23 18:38:06 -04:00
scripts Keep the smoke test compatible with macOS Bash 2026-09-13 02:16:39 -04:00
.dockerignore Add signed release pipeline 2026-09-14 23:43:13 -04:00
.git-allowed-signers Add signed release pipeline 2026-09-14 23:43:13 -04:00
.gitignore Add signed release pipeline 2026-09-14 23:43:13 -04:00
AGENTS.md Use Forgejo-compatible artifact uploads 2026-09-13 04:10:24 -04:00
CONTRIBUTING.md Initial commit 2026-08-30 01:17:26 -04:00
default.json Initial commit 2026-08-30 01:17:26 -04:00
docker-compose.yml Harden per-chain RPC access and signed transaction relay 2026-10-02 15:04:41 -04:00
Dockerfile Harden per-chain RPC access and signed transaction relay 2026-10-02 15:04:41 -04:00
gas-policy.example.json Initial commit 2026-08-30 01:17:26 -04:00
go.mod Harden per-chain RPC access and signed transaction relay 2026-10-02 15:04:41 -04:00
go.sum Harden per-chain RPC access and signed transaction relay 2026-10-02 15:04:41 -04:00
LICENSE Initial commit 2026-08-30 01:17:26 -04:00
Makefile Harden per-chain RPC access and signed transaction relay 2026-10-02 15:04:41 -04:00
minisign.pub Add signed release pipeline 2026-09-14 23:43:13 -04:00
README.md Report broadcasts below the current base fee 2026-10-23 18:38:06 -04:00
SECURITY.md Harden per-chain RPC access and signed transaction relay 2026-10-02 15:04:41 -04:00

ICU EVM MCP

A read-first EVM forensics MCP server that works on any Ethereum chain or fork. It gives an LLM Otterscan-style exploration and deep token-value forensics, with an optional gas-gated relay for already-signed transactions.

Signing and key handling remain outside the MCP server. Broadcast is disabled by default and the server never accepts a private key, keystore, secret, or credential path. The distribution includes a separate, offline icu-evm-signer executable for non-interactive local signing.

Quick start (local, stdio)

Requires Go 1.26 or later.

go build -o icu-evm-mcp ./cmd/icu-evm-mcp
go build -o icu-evm-signer ./cmd/icu-evm-signer
./icu-evm-mcp --config default.json --db icuevmmcp.db --transport stdio

To expose the signed-transaction relay, opt in explicitly:

./icu-evm-mcp --config default.json --db icuevmmcp.db --transport stdio \
  --gas-policy gas-policy.example.json --enable-broadcast

default.json seeds the database on first run (chains, endpoints, DEX config). After that the database is authoritative and you manage it at runtime through the tools (add_endpoint, upsert_chain, set_label, …).

MCP client setup

Codex

codex mcp add web3 -- /absolute/path/to/icu-evm-mcp \
  --config /absolute/path/to/default.json --db /absolute/path/to/icuevmmcp.db

Claude Code

claude mcp add web3 -- /absolute/path/to/icu-evm-mcp \
  --config /absolute/path/to/default.json --db /absolute/path/to/icuevmmcp.db

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "web3": {
      "command": "/absolute/path/to/icu-evm-mcp",
      "args": ["--config", "/absolute/path/to/default.json", "--db", "/absolute/path/to/icuevmmcp.db"]
    }
  }
}

Docker

stdio (local MCP client):

docker build -t icu-evm-mcp .
# Local stdio MCP-client command:
docker run -i --rm -v icu-evm-mcp-data:/data icu-evm-mcp

HTTP (networked / multi-client):

docker compose up --build      # serves the streamable-HTTP endpoint on :8080

The /data volume persists the database (config, labels, capabilities, bounded cache, and receipt-aggregation checkpoints) across restarts.

The image also contains /usr/local/bin/icu-evm-signer for one-shot, networkless signing. Never mount credentials into the long-running MCP container; see Non-interactive transaction signing.

Configuration (default.json)

Fully neutral — no chains are hardcoded. Each chain specifies its endpoints, wrapped-native, stablecoins, USD reference, and DEXes (V2-style routers / factories). The shipped example is PulseChain (chainId 369) + Ethereum.

{
  "defaultChain": 369,
  "chains": [{
    "chainId": 369,
    "name": "PulseChain",
    "nativeSymbol": "PLS",
    "endpoints": ["https://rpc.pulsechain.com"],
    "wrappedNative": "0xA1077a294dDE1B09bB078844df40758a5D0f9a27",
    "usdReference": "0xefD766cCb38EaF1dfd701853BFCe31359239F305",
    "dexes": [{
      "name": "PulseX V2", "version": "v2",
      "routers":   ["0x165C3410fC91EF562C50559f7d2289fEbed552d9"],
      "factories": ["0x29eA7545DEf87022BAdc76323F373EA1e707C523"]
    }]
  }]
}

Cache and receipt aggregation

The on-disk RPC cache has a 4 GiB logical cap and starts expelling cached payloads when filesystem free space reaches 1 GiB. Both thresholds are configurable, and --cache-max-size 0 keeps RPC payloads in memory only.

Use aggregate_receipts for large receipt sets. It checkpoints every completed hash and the running tally, then releases each processed batch's receipt and log payloads from the hot cache. A resumed job skips completed hashes, and passing only jobId returns its saved status. See Cache and receipt aggregation for settings and storage tradeoffs.

Gas policy

gas-policy.example.json controls the default gas mode, history size, median threshold, and optional per-chain ceiling. It is loaded at startup with --gas-policy; omitting the flag uses the same hybrid defaults without an absolute chain ceiling.

Available modes are median, request_cap, chain_policy, and hybrid. A configured chain ceiling applies in every mode. request_cap requires an exact decimal maxBlockBaseFeeGwei; hybrid accepts it optionally.

The write flow is: call gas_oracle, sign with icu-evm-signer or an existing project signer, submit only the raw signed transaction to broadcast_transaction, then confirm receipt status and canonical block membership. A wait or unknown decision never contacts a relay endpoint. A signed fee ceiling below the current or next base fee is still relayed and reported in feeWarning; such a transaction waits in the pool and holds the sender nonce. Set requireFeeCoverage to refuse it instead.

The signed transaction reaches broadcast_transaction as either rawTx (the hex) or rawTxFile (a path to a file holding only that hex). Prefer the file for a contract deployment: creation bytecode runs to tens of thousands of hex characters, and one wrong character changes what the signature covers, so the transaction is refused as coming from an address that never existed. The file must contain a signed transaction and nothing else — it is never a path to a keystore or secret, and its contents are never echoed back, including in an error.

Offline companion signer

icu-evm-signer signs complete legacy, access-list, and EIP-1559 transactions without RPC access or manual interaction. It reads an owner-only encrypted keystore and one-line secret file, verifies the requested from address, and emits only signed bytes plus a transaction preview. Literal secrets are never accepted through arguments, environment variables, stdin, or MCP.

See Non-interactive transaction signing for credential storage, transaction JSON, native and container commands, exit codes, and the required agent workflow.

The signer is also available as the standalone icu-evm-signer repository. Cloning this repository does not fetch that separate repository: the bundled signer source is already included under cmd/icu-evm-signer and is built directly with the server distribution.

Tools

Chain & node: get_chain_info, list_chains, add_endpoint, remove_endpoint, set_endpoint_priority, upsert_chain

Blocks & transactions: latest_block, get_block, gas_oracle, get_transaction, get_transaction_receipt, aggregate_receipts

Accounts: get_balance, get_code, get_nonce, get_storage_at

Tokens: token_metadata, token_balances, detect_token_standard

Deep forensics (Otterscan-parity + fallbacks): trace_transaction, get_contract_creator, get_transaction_by_sender_and_nonce, search_address_transactions, get_logs, token_transfer_history, detect_proxy, check_sourcify_verification, read_contract

DEX / value forensics: discover_pools, pool_reserves, price_token, value_received, value_sent

Address label book: set_label, delete_label, list_labels, resolve_label

Batched reads: multi_read, inspect_transactions, inspect_accounts, inspect_blocks, inspect_sender_nonces, simulate_calls, price_tokens, check_sourcify_verifications, checksum_addresses, convert_units_batch, decode_calldatas — see Batched reads below; prefer these over calling a single-target tool in a loop.

Simulation & decoding: estimate_gas, create_access_list, decode_calldata

Utilities: keccak256 (pass inputs[] to hash many at once), checksum_address, convert_units

RPC discovery and optional relay: discover_chain, suggest_rpc_endpoints, broadcast_transaction (only with --enable-broadcast)

Most address inputs accept either a hex address or a label name.

Batched reads

Prefer these over calling a single-target tool in a loop — one request instead of one per target, and the RPC work collapses into a JSON-RPC batch or a Multicall3 aggregate.

Tool Replaces looping Notes
multi_read read_contract Same function across many addresses, many functions on one address, or a mix.
inspect_transactions get_transaction, get_transaction_receipt, trace_transaction, value_received, value_sent Defaults to tx, receipt, and canonical block-membership verification.
aggregate_receipts Repeated receipt calls used only to calculate totals Checkpoints processed hashes and running totals between bounded batches.
inspect_accounts get_balance, get_nonce, get_code, get_storage_at, get_contract_creator, detect_proxy, detect_token_standard, token_balances Supports shared storage slots and token lists across all addresses.
inspect_blocks get_block Fetches number, hash, tag, or timestamp targets together.
inspect_sender_nonces get_transaction_by_sender_and_nonce Batches Otterscan lookups before bounded archive fallbacks.
simulate_calls read_contract, estimate_gas, create_access_list Mixes the three simulations across many call objects in one JSON-RPC batch.
price_tokens price_token Shares token metadata, pool discovery, reserve snapshots, and the native/USD quote.

Composite outputs preserve the single-target fields and input order, with errors attached to the affected item.

inspect_transactions includes the receipt's exact executionStatus (0x1, 0x0, or absent when unknown). Set includeRawLogs: true to receive original topics and data alongside canonical block information. logOffset and logLimit page each receipt's logs; nextLogOffset continues a receipt, while logCount remains the complete count. The default page size is 200 and the maximum is 2,000.

multi_read sends a real eth_call per entry by default, so semantics match read_contract exactly — including the shared from and stateOverrides. Setting atomic: true routes through Multicall3 so every read observes one block state; in that mode msg.sender becomes the Multicall3 contract for every sub-call, so from/stateOverrides are refused. The address is resolved from the upstream Multicall3 deployment registry, cached, and verified live; atomic mode fails explicitly if no verified deployment is available.

Exact-parity single-target tools remain registered for compatibility but are marked deprecated for new calls in their descriptions and tool metadata.

Development

make check          # the gate: fmt, vet, staticcheck, govulncheck, mod verify,
                    # test, race, build, docker build, stdio smoke test
make test           # hermetic unit tests only
make test-live      # opt-in live RPC tests (PulseChain public RPC)

make check needs a running Docker daemon, python3, a C toolchain (for -race), and network access on first run so go run can fetch the pinned staticcheck/govulncheck. Run individual targets if you can't satisfy one. See CONTRIBUTING.md.

Security

Only http/https/ws/wss RPC endpoints are accepted. Seed configuration, stored chains, runtime URLs, and relay targets require public addresses unless --allow-private-endpoints is set. This opt-in supports separate private RPC pools for multiple chains. Each pool verifies endpoint chain IDs and preserves its own endpoint priorities and failover order. Public-only connections check the resolved address at dial time and bypass environment proxies.

HTTP defaults to loopback. Non-loopback binding requires --allow-remote-http and external authentication or network controls; the flag does not provide authentication. Compose opts in inside the container and publishes on host loopback. Clients should require approval for configuration and label changes; MCP tool annotations identify these mutations.

Configured endpoint URLs are returned verbatim by list_chains, get_chain_info and suggest_rpc_endpoints, so avoid embedding provider API keys in them if callers should not see them. Keys are redacted in logs.

The configurable cache limits apply only to reusable RPC payloads. Chain configuration, labels, endpoint capabilities, and receipt-job checkpoints are not cache-pruning targets.

See SECURITY.md for the threat model and how to report an issue.

License

MIT — see LICENSE.