- Payment logic aligned with agent wallet management (e.g., ERC-4337 session keys or x402 payment flows)
- Data aggregation from multiple chain sources (indexers, oracles)
- Security enforcement to stop unauthorized or costly calls
If you want a formal intro, check what-is-mcp for the protocol spec and developer rationale.
Setting Up Your First MCP Server
Getting a baseline MCP server running isn’t rocket science but requires careful environment setup. Here’s a quick run-through using a Python setup, which I find clean for development and testing:
Prerequisites:
- Python 3.9 or newer
- Access to an Ethereum testnet RPC (e.g., Goerli) or an L2 environment
- Basic knowledge of FastAPI or Flask (for REST) or WebSocket handling
Install dependencies:
pip install fastapi uvicorn web3 pydantic
Minimal MCP server snippet:
from fastapi import FastAPI, HTTPException
from web3 import Web3
app = FastAPI()
rpc_url = "https://goerli.infura.io/v3/YOUR_INFURA_KEY"
w3 = Web3(Web3.HTTPProvider(rpc_url))
@app.get("/balance/{address}")
async def get_balance(address: str):
if not w3.isAddress(address):
raise HTTPException(status_code=400, detail="Invalid address")
balance = w3.eth.get_balance(address)
return {"balance_wei": balance, "balance_eth": w3.fromWei(balance, 'ether')}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
This example exposes a simple balance check endpoint to the agent.
Next, integrate your AI model calls and payment protocols on top, referencing how to wire up wallets at mcp-wallet-integration.
In production, I switched to async-compatible versions of the Web3 client and added structured logging for debugging. Also, don’t forget rate limiting and API key handling (x402-style) for request control.
Key Components: Wallet Integration and Data Sources
From my experience, two pillars underpin an MCP server’s utility:
1. Wallet Integration
Automated agents need to sign transactions or calls without exposing long-lived private keys. This is where session keys and spending limits become your friends.
For example, using ERC-4337 smart contract wallets allows agents to operate with scoped permissions and gas sponsoring while preventing full wallet compromise. I wrote a small helper wrapping a wallet client to abstract session key renewal.
// Pseudo-code for wallet session key rotation
async function rotateSessionKey(walletAddress, oldKey, newKey) {
// Call wallet’s smart contract method to update session key
const tx = await walletContract.methods.updateSessionKey(newKey).send({ from: walletAddress });
return tx.transactionHash;
}
This avoids the risk of unlimited approvals or unbounded spending — both massive attack surfaces if misconfigured.
2. Blockchain Data Sources
An MCP server aggregates information from:
- RPC endpoints: Direct blockchain node queries
- Indexers: E.g., The Graph, custom subgraph setups
- Oracles: External data feeds for prices, events
Each has trade-offs. RPC is real-time but rate-limited. Indexers offer fast queries but delay finality. Oracles add extra trust assumptions.
I maintain a config layer caching fallback sources to keep the AI agent responsive during chain congestion or downtime. See blockchain-data-sources-for-mcp for a detailed comparison.
Security Best Practices for MCP Servers
Since MCP servers act as gatekeepers for agent requests and payments, they attract threat actors:
- Private key leakage: Never store the full seed phrase on the server. Use hardware modules or environment-encrypted secrets.
- Unlimited approvals: Avoid open-ended ERC-20/uniswap approvals. Use wrappers that cap spending.
- Replay attacks: Implement nonce tracking on API requests to block duplicate actions.
- Mainnet caution: Always deploy first on testnets, even with dummy data, before risking real assets.
A typical gotcha I hit was reentrancy in payment fallback mechanisms, which Slither flags during simple audit runs. Integrating static analyzers early in your CI pipeline helps catch these before deployment.
For more, check mcp-server-security-best-practices.
Comparing MCP Server Frameworks and Tools
Here’s a quick table comparing some known MCP tooling options I’ve evaluated (may not be exhaustive and check versions):
| Tool/Framework |
Language |
Chain Support |
License |
Maturity |
Notes |
| Base MCP Server |
Python |
EVM (Goerli, Mainnet) |
MIT |
Early alpha |
Good for rapid dev, less built-in session management |
| Solana MCP Kit |
Rust |
Solana |
Apache-2.0 |
Beta |
Focused on Solana agents, requires Rust setup |
| x402 Relay |
TypeScript |
Multi-chain (EVM/L2) |
GPLv3 |
Proof of concept |
Rich payment protocol support but needs audit |
If your use case targets Solana or EVM specifically, pick accordingly. Many devs (myself included) pick Python for ease of prototyping but rewrite critical paths in Rust or TS for production speed.
See mcp-server-tools-comparison for a deeper dive.
Troubleshooting Common MCP Server Issues
Running an MCP server smoothly can be tricky. A few common headaches I’ve hit:
- "Invalid signature" errors when the wallet client is out of sync with chain nonce or session key expired. Always verify key rotation logic.
- Rate limits/bandwidth exceeded on RPC providers. Set up a fallback RPC pool with load balancers.
- Data staleness from indexers causing models to act on outdated contexts. Implement query timestamps and cache invalidation.
- Cross-chain transaction rejections. Check chain ID and network params carefully on client side.
Check mcp-server-troubleshooting for targeted fixes.
Building an EVM MCP Server with viem: A Hands-On Walkthrough
Whenever I stand up an MCP server for EVM chains, I reach for viem because its typed JSON-RPC client keeps tool schemas honest and the bundle small. My rule is simple: expose deterministic read tools first, then gate every write behind an explicit host confirmation so the language model never moves funds on its own.
1. Define the client and a read tool
import { createPublicClient, http, formatEther } from "viem";
import { mainnet } from "viem/chains";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const client = createPublicClient({ chain: mainnet, transport: http(process.env.RPC_URL) });
const server = new McpServer({ name: "evm-mcp", version: "1.0.0" });
server.tool("get_balance", { address: z.string() }, async ({ address }) => {
const wei = await client.getBalance({ address });
return { content: [{ type: "text", text: `${formatEther(wei)} ETH` }] };
});
2. Add a guarded write tool
For send_transaction I load a WalletClient from an env-scoped key, but the tool only prepares the transaction and returns it for approval — signing happens after a human or policy engine signs off.
What I always do:
- Pin
chainId inside each tool so the model can't target the wrong network.
- Return raw hex and a plain-English summary; agents reason better with both.
- Rate-limit and cache RPC reads, because public endpoints throttle quickly.
This layering gives me an auditable server where reads are cheap and writes are impossible to trigger by accident.
Connecting a Solana MCP Server: RPC Reads and Signed Transactions
Solana's account model behaves nothing like EVM, so I structure its MCP server around @solana/web3.js and a Connection pointed at a reliable RPC. Because Solana transactions expire once the recent blockhash ages out, I keep signing tools fast and stateless — fetch a fresh blockhash inside the tool call, never cache it.
Reading account state
import { Connection, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
const conn = new Connection(process.env.SOLANA_RPC, "confirmed");
server.tool("sol_balance", { pubkey: z.string() }, async ({ pubkey }) => {
const lamports = await conn.getBalance(new PublicKey(pubkey));
return { content: [{ type: "text", text: `${lamports / LAMPORTS_PER_SOL} SOL` }] };
});
Signing a transfer safely
For transfers I build the instruction, attach a freshly fetched blockhash, and return the serialized, unsigned transaction. The host wallet — or a Squads multisig — signs and broadcasts, so my server never holds the keypair in a long-lived process.
Best practices I follow:
- Use
"confirmed" commitment for tool reads; reserve "finalized" for balance-critical confirmations.
- Surface the compute-unit price so agents can reason about priority fees during congestion.
- Validate every base58 pubkey before it reaches an instruction — malformed keys crash the RPC call.
Treating reads as cheap and signing as a deliberate, host-approved step keeps the whole agent loop predictable and safe.
Choosing an MCP Transport for On-Chain Tools: stdio vs HTTP+SSE
Transport choice shapes how safely an MCP server can touch a blockchain, and I pick it based on where the keys live and who calls the server. The three options I evaluate are local stdio, HTTP + SSE, and the newer Streamable HTTP.
| Transport |
Best for |
Auth model |
Key-handling risk |
| stdio |
Local dev, desktop agents |
Process isolation |
Low — keys stay on device |
| HTTP + SSE |
Shared / team servers |
Bearer token or OAuth |
Medium — network-exposed |
| Streamable HTTP |
Scalable, multi-client |
OAuth + session IDs |
Medium — needs strict CORS |
How I decide
For a wallet-signing server I default to stdio: the private key never leaves the machine and there is no network surface to attack. The moment I need a hosted server that multiple agents reach, I move to Streamable HTTP with OAuth and per-session isolation.
Security rules I enforce regardless of transport:
- Terminate TLS in front of any HTTP transport — never expose SSE over plain HTTP.
- Bind local servers to
127.0.0.1, not 0.0.0.0.
- Scope tokens to read-only tools by default; require a separate credential to unlock signing.
- Log every tool invocation with the caller's session ID for after-the-fact audits.
Getting transport right early saves painful re-architecture once real funds start flowing through the agent.
Summary and Next Steps
At this point, you have a practical overview of MCP servers in Web3 — what they do, how to spin one up focusing on Python, wallet integration, data sources, plus security and tooling trade-offs.
If you want to build a full server quickly, consider the step-by-step Python tutorial at how-to-build-web3-mcp-server-python. From there, adding Solana support or connecting AI models like Claude follows naturally with solana-mcp-claude-integration.
To wrap up, MCP servers are evolving rapidly. You’ll want to keep an eye on protocol upgrades (think ERC-7579 or ERC-8004) that add flexibility to on-chain AI interaction patterns.
So, ready to get your hands dirty building a blockchain-aware AI backend? Let me know how your first MCP server goes — the field needs more real-world code and feedback.
Explore the detailed setups, security guides, and integrations by visiting our internal hubs:
Happy coding!