-
Notifications
You must be signed in to change notification settings - Fork 72
Description
boa-mcp: MCP Server for Vyper Development with Titanoboa
3 tools, local + fork + live chain, zero web3.py.
What This Is
An MCP server that lets AI agents compile Vyper contracts and interact with them via Titanoboa.
Design
- Boa does everything. Titanoboa is the single dependency for all EVM interaction. One exception:
eth_account.Accountfor loading private keys in network mode (boa transitive dep). - Three modes, same API.
boa.load()/contract.function()work in all three. The server switches modes via environment setup:- Local (default): py-evm sandbox. No RPC, no key.
- Fork: RPC URL set, no private key. Reads real chain state, writes stay local.
- Network: RPC URL + private key. Real transactions.
- Explicit broadcasting. Live chain operations require
broadcast: true. No accidental real transactions.
Out of Scope (v1)
Hardware wallets, multi-account, event subscriptions, Etherscan ABI fetching, multiple compiler versions, tx replacement.
Tech Stack
- Python 3.11+,
mcp(stdio transport),titanoboa>=0.2.8
Configuration
Environment variables. No config file.
| Variable | Description |
|---|---|
BOA_MCP_RPC_URL |
Ethereum RPC endpoint. Required for fork and network modes. |
BOA_MCP_PRIVATE_KEY |
Private key for network mode. Hex string, with or without 0x. |
BOA_MCP_EVM_VERSION |
Default EVM version. Default: cancun. |
On startup, log mode to stderr. No stdout until MCP client connects.
Usage:
# Local mode (no config needed)
boa-mcp
# Fork mode
export BOA_MCP_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
boa-mcp
# Network mode (real transactions)
export BOA_MCP_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
export BOA_MCP_PRIVATE_KEY=0xYOUR_PRIVATE_KEY
boa-mcpTools
vyper_compile
Compile Vyper source to bytecode and ABI. Supports multi-file projects.
Inputs:
| Parameter | Required | Description |
|---|---|---|
sources |
Yes | object - filename to source code map |
main |
Yes | string - main contract filename |
evm_version |
No | string - target EVM version |
Outputs:
| Field | Type | Description |
|---|---|---|
bytecode |
string |
hex with 0x prefix |
abi |
array |
JSON ABI (includes constructor entry if present) |
Write all sources to a temp directory, call boa.load_partial() with compiler_args={'evm_version': v} if set.
Done when:
- Single-file contract compiles
- Multi-file contract with imports compiles
- Compilation errors return file, line, column
- ABI includes constructor entry when present
boa_deploy
Compile and deploy a Vyper contract to local, forked, or live EVM.
Inputs:
| Parameter | Required | Description |
|---|---|---|
sources |
Yes | object - filename to source code map |
main |
Yes | string - main contract filename |
args |
No | array - constructor arguments |
rpc_url |
No | string - RPC URL. Falls back to BOA_MCP_RPC_URL. |
broadcast |
No | boolean - deploy to live chain. Default: false. |
Outputs:
| Field | Type | Description |
|---|---|---|
address |
string |
EIP-55 checksummed |
abi |
array |
contract ABI |
gas_used |
integer |
gas consumed |
mode |
string |
"local", "fork", or "network" |
tx_hash |
string|null |
transaction hash (network mode only) |
Mode logic: broadcast=true + RPC + key → boa.set_network_env() + add_account() (network). RPC without broadcast → boa.fork() (fork). Neither → local py-evm. Then boa.load() to compile and deploy.
Store deployed address -> contract object in memory for boa_interact.
Done when:
- Deploys in local mode
- Deploys in fork mode
- Deploys in network mode (
broadcast=true), returnstx_hash -
broadcast=truewithout key or RPC returns error - Multi-file contracts deploy
boa_interact
Call any function on a deployed contract.
Inputs:
| Parameter | Required | Description |
|---|---|---|
address |
Yes | string - contract address from boa_deploy |
function |
Yes | string - function name |
args |
No | array - function arguments |
broadcast |
No | boolean - real transaction on live chain. Default: false. |
Outputs:
| Field | Type | Description |
|---|---|---|
result |
any |
decoded return value (JSON-serialized) |
gas_used |
integer |
gas consumed |
success |
boolean |
whether the call succeeded |
error |
string|null |
revert reason if failed |
tx_hash |
string|null |
transaction hash (network mode, state-changing only) |
Look up contract by address, call getattr(contract, function_name)(*args). Serialize return values to JSON (integers as strings to prevent precision loss, bytes as hex, structs via _asdict()).
Done when:
- View function returns result
- State-changing function modifies state
- Revert returns
success: falsewith error message -
broadcast=truesends real transaction withtx_hash -
broadcast=trueon view function works (free read) -
broadcast=truewithout key returns error
Acceptance Tests
Test 1: Compile
{
"sources": {"counter.vy": "#pragma version ~=0.4.0\ncount: public(uint256)\n@external\ndef increment():\n self.count += 1"},
"main": "counter.vy"
}Returns bytecode and abi.
Test 2: Multi-File Compile
Compile a contract that imports an interface from a separate file.
Test 3: Deploy and Interact (Local)
-
boa_deploywith counter source, no RPC -> local deploy -
boa_interactcount->'0' -
boa_interactincrement -
boa_interactcount->'1'
Test 4: Deploy to Fork
-
boa_deploywithrpc_urlpointing to Sepolia - Verify
mode: "fork"
Test 5: Network Deploy + Interact
-
broadcast: truewith Sepolia RPC + key -> deploys, returnstx_hash -
broadcast: trueonincrement->tx_hash -
count(view, no broadcast) ->'1'
Test 6: Broadcast Safety
-
broadcast: truewithout key -> error - Default (no broadcast) with RPC -> fork mode, NOT live chain
Test 7: stdout Cleanliness
- Every line on stdout is valid JSON-RPC (no stray prints from boa/vyper)
Future Expansion (Not v1)
Multi-account, hardware wallets, Etherscan ABI fetch, event log decoding, snapshot/revert (boa.env.anchor()), deployments persistence (boa.deployments.DeploymentsDB).