infura-mcp-server
Version:
Model Context Protocol (MCP) server providing comprehensive read-only Ethereum blockchain access through Infura's infrastructure. Connect Claude Desktop, VS Code, and Cursor to 29 secure Ethereum JSON-RPC tools across 30+ networks including Ethereum, Poly
189 lines (180 loc) • 7.83 kB
JavaScript
/**
* @fileoverview Tool for querying event logs from smart contracts via Infura JSON-RPC API.
* @module tools/eth-get-logs
*
* This tool retrieves event logs emitted by smart contracts within a specified block range.
* Logs can be filtered by contract address and indexed event parameters (topics). This is
* essential for tracking token transfers, contract events, and blockchain activity.
*
* Common use cases include:
* - Tracking ERC-20 Transfer events (topic: 0xddf252ad...)
* - Monitoring DEX swaps and liquidity events
* - Auditing contract state changes
* - Building event-driven applications
*
* @see {@link https://docs.infura.io/api/networks/ethereum/json-rpc-methods/eth_getlogs|Infura eth_getLogs docs}
* @see {@link https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getlogs|Ethereum JSON-RPC spec}
*
* @example
* // Get all logs from a specific block range
* const result = await eth_getLogs({
* fromBlock: "0x10d4f",
* toBlock: "0x10d50",
* network: "mainnet"
* });
*
* @example
* // Filter ERC-20 Transfer events from USDT contract
* const result = await eth_getLogs({
* fromBlock: "latest",
* toBlock: "latest",
* address: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
* topics: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],
* network: "mainnet"
* });
* // Returns: { logs: [...], pagination: { total, count, offset, limit, has_more, next_offset } }
*
* @example
* // Paginate through large result sets
* const result = await eth_getLogs({
* fromBlock: "0x10d4f",
* toBlock: "0x10d50",
* limit: 100,
* offset: 0
* });
* // Use result.pagination.next_offset for the next page if result.pagination.has_more is true
*/
import { callInfura } from "../lib/infura-client.js";
import { validateAddress, validateBlockTag } from "../lib/validators.js";
/**
* Executes the eth_getLogs JSON-RPC call to retrieve filtered event logs.
*
* @param {Object} args - The function arguments.
* @param {string} args.fromBlock - Starting block: hex number (e.g., "0x10d4f") or tag ("latest", "earliest", "pending", "safe", "finalized").
* @param {string} args.toBlock - Ending block: hex number or tag.
* @param {string} [args.address] - Optional contract address to filter logs from (0x-prefixed, 40 hex chars).
* @param {Array<string|null>} [args.topics=[]] - Array of 32-byte topic filters for indexed event parameters. Use null for wildcards.
* @param {string} [args.network="mainnet"] - Target Ethereum network (e.g., "mainnet", "sepolia", "holesky").
* @param {number} [args.limit=1000] - Maximum number of logs to return (1-10000). Use with offset for pagination.
* @param {number} [args.offset=0] - Number of logs to skip for pagination.
* @returns {Promise<Object>} Object containing logs array and pagination metadata.
* @throws {ValidationError} If fromBlock or toBlock format is invalid.
* @throws {ValidationError} If address is provided but has invalid format.
* @throws {McpError} If the query range is too large or Infura API fails.
*/
const executeFunction = async ({
fromBlock,
toBlock,
address,
topics = [],
network = "mainnet",
limit = 1000,
offset = 0,
}) => {
validateBlockTag(fromBlock, 'fromBlock');
validateBlockTag(toBlock, 'toBlock');
// Address is optional per Ethereum JSON-RPC spec (can get logs from all contracts)
if (address) {
validateAddress(address);
}
// Validate and clamp pagination parameters
const validLimit = Math.max(1, Math.min(10000, Number(limit) || 1000));
const validOffset = Math.max(0, Number(offset) || 0);
const params = [
{
fromBlock,
toBlock,
...(address && { address }),
topics,
},
];
// Call Infura API (does not support pagination natively)
const allLogs = await callInfura("eth_getLogs", params, network);
// Apply pagination to results
const total = allLogs.length;
const paginatedLogs = allLogs.slice(validOffset, validOffset + validLimit);
const hasMore = validOffset + paginatedLogs.length < total;
// Return with pagination metadata
return {
logs: paginatedLogs,
pagination: {
total,
count: paginatedLogs.length,
offset: validOffset,
limit: validLimit,
has_more: hasMore,
next_offset: hasMore ? validOffset + paginatedLogs.length : null,
},
};
};
/**
* Tool configuration for retrieving logs from Ethereum using Infura.
* @type {Object}
*/
const apiTool = {
function: executeFunction,
definition: {
type: "function",
function: {
name: "eth_getLogs",
description:
"Query event logs emitted by smart contracts with flexible filters. Supports pagination for large result sets.\n\nArgs:\n - fromBlock (string): Start block as hex (e.g., '0x10d4f') or tag ('latest', 'earliest', 'pending').\n - toBlock (string): End block as hex or tag.\n - address (string, optional): Contract address to filter logs from.\n - topics (array, optional): Array of 32-byte topic filters for indexed event parameters.\n - network (string, optional): Ethereum network to query. Defaults to 'mainnet'.\n - limit (integer, optional): Maximum logs to return (1-10000). Defaults to 1000.\n - offset (integer, optional): Number of logs to skip for pagination. Defaults to 0.\n\nReturns:\n - Object with 'logs' array and 'pagination' metadata (total, count, offset, limit, has_more, next_offset).\n\nExamples:\n - \"Get all logs in block range\": { \"fromBlock\": \"0x10d4f\", \"toBlock\": \"0x10d50\" }\n - \"Filter by contract and topic\": { \"fromBlock\": \"latest\", \"toBlock\": \"latest\", \"address\": \"0xdAC17F958D2ee523a2206206994597C13D831ec7\", \"topics\": [\"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\"] }\n - \"Paginate results\": { \"fromBlock\": \"0x10d4f\", \"toBlock\": \"0x10d50\", \"limit\": 100, \"offset\": 0 }\n\nErrors:\n - InvalidParams: When block tags or address format is invalid.\n - InternalError: When query range is too large or Infura API fails.",
parameters: {
type: "object",
properties: {
fromBlock: {
type: "string",
description: "The starting block number or block identifier.",
},
toBlock: {
type: "string",
description: "The ending block number or block identifier.",
},
address: {
type: "string",
description: "The address of the contract to filter logs.",
},
topics: {
type: "array",
items: {
type: "string",
},
description: "An array of topics to filter logs.",
},
network: {
type: "string",
description: "The Ethereum network to query, e.g., 'mainnet' or 'sepolia'.",
default: "mainnet",
},
limit: {
type: "integer",
description: "Maximum number of logs to return (default: 1000, max: 10000). Use with offset for pagination.",
default: 1000,
minimum: 1,
maximum: 10000,
},
offset: {
type: "integer",
description: "Number of logs to skip for pagination (default: 0).",
default: 0,
minimum: 0,
},
response_format: {
type: "string",
enum: ["json", "markdown"],
description: "Output format: 'json' for structured data, 'markdown' for human-readable.",
default: "json",
},
},
required: ["fromBlock", "toBlock"],
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
},
},
};
export { apiTool };