Read-first EVM forensics MCP server with RPC discovery and an optional gas-gated signed-transaction relay.
  • Go 98.9%
  • Shell 0.5%
  • Makefile 0.4%
  • Dockerfile 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
dev 86910243c5
Some checks failed
release / release (push) Failing after 31m39s
Bound cache and add resumable receipt aggregation
Cap persistent RPC payloads at 4 GiB by default, reserve 1 GiB of free space, and expose configurable disk, memory, and pruning thresholds. Keep memory-only operation available while protecting non-cache database state.

Add deterministic receipt batching with durable per-hash checkpoints and running totals. Completed batches release hot receipt data, retries skip processed hashes, and completed jobs collapse to their final result.

Document the storage tradeoffs and update release packaging for v0.2.0.
2026-09-18 18:11:33 -04:00
.agents/skills/icu-evm-mcp Add signed release pipeline 2026-09-14 23:43:13 -04:00
.forgejo/workflows Bound cache and add resumable receipt aggregation 2026-09-18 18:11:33 -04:00
cmd Bound cache and add resumable receipt aggregation 2026-09-18 18:11:33 -04:00
docs Bound cache and add resumable receipt aggregation 2026-09-18 18:11:33 -04:00
internal Bound cache and add resumable receipt aggregation 2026-09-18 18:11:33 -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
CHANGELOG.md Bound cache and add resumable receipt aggregation 2026-09-18 18:11:33 -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 Use Forgejo-compatible artifact uploads 2026-09-13 04:10:24 -04:00
Dockerfile Add signed release pipeline 2026-09-14 23:43:13 -04:00
gas-policy.example.json Initial commit 2026-08-30 01:17:26 -04:00
go.mod Add signed release pipeline 2026-09-14 23:43:13 -04:00
go.sum Add signed release pipeline 2026-09-14 23:43:13 -04:00
LICENSE Initial commit 2026-08-30 01:17:26 -04:00
Makefile Add signed release pipeline 2026-09-14 23:43:13 -04:00
minisign.pub Add signed release pipeline 2026-09-14 23:43:13 -04:00
README.md Bound cache and add resumable receipt aggregation 2026-09-18 18:11:33 -04:00
SECURITY.md Accept a signed transaction from a file, not only by value 2026-08-30 17:36:11 -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)

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.

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

Endpoint URLs supplied at runtime are validated: only http/https/ws/wss are dialed, and they must resolve to public addresses unless --allow-private-endpoints is set. Endpoints from your config file are not address-restricted, so pointing at a node on your LAN works as it always did.

Do not bind --http-addr to a public interface — the HTTP transport has no authentication and the tool surface includes add_endpoint and upsert_chain. It defaults to loopback.

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.