npm package: @missionsquad/mcp-evm
Description: MCP server for Ethereum Virtual Machine (EVM) RPC API
Author: Mission Squad AI
Latest version: 1.0.2

# @missionsquad/mcp-evm

MCP server for Ethereum Virtual Machine (EVM) RPC APIs, powered by `evmdecoder`.

This server exposes curated EVM read/decoding tools for LLM clients while keeping RPC endpoints hidden from tool schemas.

## Features

- FastMCP stdio server (`mcp-evm`)
- Hidden per-request RPC URL support via `context.extraArgs.rpcUrl`
- Fallback RPC URL via `EVM_RPC_URL`
- Per-RPC decoder instance pooling with LRU eviction
- Curated tool surface (15 tools) to avoid oversized payloads
- Timestamp normalization to human-readable UTC text before returning results
- Type-safe schemas with strict TypeScript
- CI build/test and npm publish GitHub Actions

## Tool Surface

The server currently exposes these tools:

- `evm_decode_function_call`
- `evm_decode_function_call_v2`
- `evm_contract_info`
- `evm_get_block`
- `evm_get_slim_block`
- `evm_get_slim_block_by_hash`
- `evm_get_full_block`
- `evm_get_pending_transactions`
- `evm_get_pending_transaction`
- `evm_get_transaction`
- `evm_get_transaction_receipt`
- `evm_get_internal_transaction`
- `evm_get_internal_transactions_by_block`
- `evm_get_fee_history`
- `evm_get_latest_block_number`

Notes:

- `initialize` is not exposed as a tool; decoder initialization is internal.
- High-volume/batch tools and raw processing utilities are intentionally not exposed.
- `evm_get_pending_transactions` defaults to `decode: false` to reduce response size.

## Sample Data

Sample payloads for a subset of tools are available in [`sample-data/`](sample-data/).  
Not every tool currently has sample data.

Available samples:

- `evm_contract_info`: [`sample-data/evm_contract_info.json`](sample-data/evm_contract_info.json)
- `evm_decode_function_call`: [`sample-data/evm_decode_function_call.json`](sample-data/evm_decode_function_call.json)
- `evm_decode_function_call_v2`: [`sample-data/evm_decode_function_call_v2.json`](sample-data/evm_decode_function_call_v2.json)
- `evm_get_block`:
  - decoded: [`sample-data/evm_get_block(decode).json`](sample-data/evm_get_block%28decode%29.json)
  - raw: [`sample-data/evm_get_block(raw).json`](sample-data/evm_get_block%28raw%29.json)
- `evm_get_slim_block`: [`sample-data/evm_get_slim_block.json`](sample-data/evm_get_slim_block.json)
- `evm_get_transaction`: [`sample-data/evm_get_transaction.json`](sample-data/evm_get_transaction.json)
- `evm_get_transaction_receipt`: [`sample-data/evm_get_transaction_receipt.json`](sample-data/evm_get_transaction_receipt.json)
- `evm_get_internal_transaction`: [`sample-data/evm_get_internal_transaction.json`](sample-data/evm_get_internal_transaction.json)
- `evm_get_fee_history`: [`sample-data/get_fee_history.json`](sample-data/get_fee_history.json)

## Requirements

- Node.js `>=20`
- Yarn (project scripts use Yarn)

## Installation

### Local Development

```bash
yarn install --frozen-lockfile
yarn build
yarn start
```

### As a CLI (after install/publish)

```bash
mcp-evm
```

The server runs over stdio transport.

## RPC URL Resolution

Each tool call resolves RPC URL in this order:

1. Hidden extra argument: `rpcUrl` (preferred)
2. Environment fallback: `EVM_RPC_URL`

If neither is present, the tool throws a user-facing error.

Important: `rpcUrl` is intentionally not part of public tool schemas.

## Configuration

Copy `.env.example` to `.env` and set values as needed.

### Environment Variables

| Variable | Required | Default |
|---|---|---|
| `EVM_RPC_URL` | No | unset |
| `EVM_ABI_DIRECTORY` | No | `../abis` relative to built files |
| `EVM_HTTP_TIMEOUT_MS` | No | `60000` |
| `EVM_HTTP_VALIDATE_CERTIFICATE` | No | `false` |
| `EVM_HTTP_KEEP_ALIVE` | No | `true` |
| `EVM_HTTP_MAX_SOCKETS` | No | `256` |
| `EVM_HTTP_MAX_RETRIES` | No | `10` |
| `EVM_HTTP_MAX_BATCH_SPLITS` | No | `15` |
| `EVM_CLIENT_MAX_BATCH_SIZE` | No | `100` |
| `EVM_CLIENT_MAX_BATCH_TIME` | No | `0` |
| `EVM_CLIENT_INDIVIDUAL_RECEIPTS` | No | `true` |
| `EVM_CLIENT_MAX_RETRY_TIME` | No | `10000` |
| `EVM_CLIENT_TRACER_TIMEOUT` | No | `100` |
| `EVM_CONTRACT_INFO_MAX_CACHE_ENTRIES` | No | `25000` |
| `EVM_EXPERIMENTAL` | No | `false` |
| `EVM_DECODER_POOL_MAX_ENTRIES` | No | `25` |

### ABI Directory Behavior

- If `EVM_ABI_DIRECTORY` exists, it is used for ABI discovery.
- If configured/default ABI directory does not exist, the server falls back cleanly and continues with built-in anonymous signature support from `evmdecoder`.
- `abis.zip` is committed and published, and is automatically extracted into `./abis` during package install via `postinstall`.
- Manual extraction is also available:

```bash
yarn extract:abis
```

## Tool Reference

### `evm_decode_function_call`
Decode EVM function input.

Parameters:
- `input: string`
- `address?: string`

Sample:
- [`sample-data/evm_decode_function_call.json`](sample-data/evm_decode_function_call.json)

### `evm_decode_function_call_v2`
Decode function input and include contract info.

Parameters:
- `input: string`
- `address?: string`

Sample:
- [`sample-data/evm_decode_function_call_v2.json`](sample-data/evm_decode_function_call_v2.json)

### `evm_contract_info`
Classify/inspect a contract address.

Parameters:
- `address: string`

Sample:
- [`sample-data/evm_contract_info.json`](sample-data/evm_contract_info.json)

### `evm_get_block`
Get block with decoded transactions by default.

Parameters:
- `blockNumber: number`
- `decode?: boolean` (default `true`)

Samples:
- decoded: [`sample-data/evm_get_block(decode).json`](sample-data/evm_get_block%28decode%29.json)
- raw: [`sample-data/evm_get_block(raw).json`](sample-data/evm_get_block%28raw%29.json)

### `evm_get_slim_block`
Get slim block.

Parameters:
- `blockNumber: number`
- `raw?: boolean` (default `true`)

Sample:
- [`sample-data/evm_get_slim_block.json`](sample-data/evm_get_slim_block.json)

### `evm_get_slim_block_by_hash`
Get slim block by hash.

Parameters:
- `hash: string`
- `raw?: boolean` (default `true`)

### `evm_get_full_block`
Get full block with receipts.

Parameters:
- `blockNumber: number`
- `decode?: boolean` (default `true`)

### `evm_get_pending_transactions`
Get pending transactions; decode is off by default.

Parameters:
- `decode?: boolean` (default `false`)

### `evm_get_pending_transaction`
Get one pending transaction by hash.

Parameters:
- `hash: string`
- `decode?: boolean` (default `true`)

### `evm_get_transaction`
Get transaction by hash.

Parameters:
- `hash: string`
- `decode?: boolean` (default `true`)

Sample:
- [`sample-data/evm_get_transaction.json`](sample-data/evm_get_transaction.json)

### `evm_get_transaction_receipt`
Get transaction receipt.

Parameters:
- `hash: string`
- `decode?: boolean` (default `true`)

Sample:
- [`sample-data/evm_get_transaction_receipt.json`](sample-data/evm_get_transaction_receipt.json)

### `evm_get_internal_transaction`
Get internal trace for one transaction.

Parameters:
- `hash: string`
- `decode?: boolean` (default `true`)

Sample:
- [`sample-data/evm_get_internal_transaction.json`](sample-data/evm_get_internal_transaction.json)

### `evm_get_internal_transactions_by_block`
Get internal traces by block number.

Parameters:
- `blockNumber: number`
- `decode?: boolean` (default `true`)

### `evm_get_fee_history`
Get fee history.

Parameters:
- `blockCount: number`
- `blockTarget: number`

Sample:
- [`sample-data/get_fee_history.json`](sample-data/get_fee_history.json)

### `evm_get_latest_block_number`
Get latest block number.

Parameters:
- none

## Timestamp Normalization

Before returning tool results, any field named `timestamp` is normalized recursively to human-readable UTC text:

- Supports numeric epoch seconds
- Supports numeric epoch milliseconds
- Supports numeric strings and hex strings (for example `0x...`)
- Output format: `YYYY-MM-DD HH:mm:ss.sss UTC`

All non-`timestamp` fields are returned unchanged.

## Response Format

Tool handlers return deterministic JSON text (stringified payload). In MCP responses, this appears as text content and should be parsed by clients that need structured access.

Response normalization also applies two token-optimization/enrichment rules:

- If a formatted transaction includes decoded `call`, the raw `input` field is removed.
- If decoded transactions/logs are missing `call`/`event`, the server attempts to enrich them before returning.

## Usage Examples

### JSON-RPC `tools/call` with hidden RPC URL

```json
{
  "method": "tools/call",
  "params": {
    "name": "evm_get_block",
    "arguments": {
      "blockNumber": 19000000,
      "decode": true,
      "rpcUrl": "https://mainnet.infura.io/v3/<key>"
    }
  }
}
```

### JSON-RPC `tools/call` using `EVM_RPC_URL` fallback

```json
{
  "method": "tools/call",
  "params": {
    "name": "evm_get_latest_block_number",
    "arguments": {}
  }
}
```

### FastMCP-style client call

```ts
await client.callTool('evm_get_transaction', {
  hash: '0x...',
  decode: true,
  rpcUrl: 'https://mainnet.infura.io/v3/<key>',
})
```

## Development

### Scripts

- `yarn build` - clean + compile TypeScript + make CLI executable
- `yarn start` - run built server
- `yarn dev` - watch compile and run
- `yarn inspect` - inspect MCP server with FastMCP inspector
- `yarn test` - run unit tests
- `yarn test:watch` - run tests in watch mode
- `yarn test:coverage` - run tests with V8 coverage report
- `yarn format` - check Prettier formatting
- `yarn format:write` - apply formatting

### Coverage

```bash
yarn test:coverage
```

Vitest writes a coverage report under `coverage/`.

## CI/CD

Workflows are in `.github/workflows/`:

- `build.yaml`: build + test on pull requests
- `publish.yaml`: build + test + publish to npm on `main` pushes (ignoring markdown-only changes)

Publish job requires repository secret:

- `NPM_TOKEN` (used as `NODE_AUTH_TOKEN`)

## npm Publishing

Package is configured for scoped public publish:

- name: `@missionsquad/mcp-evm`
- `publishConfig.access`: `public`
- `prepublishOnly`: runs tests and build before publish

Manual publish (if needed):

```bash
npm publish --access public
```

## Troubleshooting

### `RPC URL is required...`
Provide hidden `rpcUrl` in tool arguments or set `EVM_RPC_URL`.

### ABI directory not found
If you want local ABI matching, set `EVM_ABI_DIRECTORY` to an existing folder or run `yarn extract:abis`.

### Large responses
Use slim/single-item tools and keep decode flags intentional. `evm_get_pending_transactions` defaults to `decode: false` for this reason.

## About Mission Squad

[Mission Squad](https://missionsquad.ai) provides a platform for building and running AI agents with production-ready tooling, including MCP server integrations like this one.  
You can configure MCP servers, connect external systems, and create specialized agents quickly without rebuilding core infrastructure each time.

## License

MIT