Skip to main content
Glama
blockscout

Blockscout MCP Server

Official

read_contract

Read-only

Query smart contract functions to retrieve blockchain data like token balances or contract states using chain ID, address, ABI, and function parameters.

Instructions

    Calls a smart contract function (view/pure, or non-view/pure simulated via eth_call) and returns the
    decoded result.

    This tool provides a direct way to query the state of a smart contract.

    Example:
    To check the USDT balance of an address on Ethereum Mainnet, you would use the following arguments:
{
  "tool_name": "read_contract",
  "params": {
    "chain_id": "1",
    "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
    "abi": {
      "constant": true,
      "inputs": [{"name": "_owner", "type": "address"}],
      "name": "balanceOf",
      "outputs": [{"name": "balance", "type": "uint256"}],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    },
    "function_name": "balanceOf",
    "args": "["0xF977814e90dA44bFA03b6295A0616a897441aceC"]"
  }
}

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
chain_idYesThe ID of the blockchain
addressYesSmart contract address
abiYesThe JSON ABI for the specific function being called. This should be a dictionary that defines the function's name, inputs, and outputs. The function ABI can be obtained using the `get_contract_abi` tool.
function_nameYesThe symbolic name of the function to be called. This must match the `name` field in the provided ABI.
argsNoA JSON string containing an array of arguments. Example: "["0xabc..."]" for a single address argument, or "[]" for no arguments. Order and types must match ABI inputs. Addresses: use 0x-prefixed strings; Numbers: prefer integers (not quoted); numeric strings like "1" are also accepted and coerced to integers. Bytes: keep as 0x-hex strings.[]
blockNoThe block identifier to read the contract state from. Can be a block number (e.g., 19000000) or a string tag (e.g., 'latest'). Defaults to 'latest'.latest

Implementation Reference

  • The primary handler function for the 'read_contract' tool. It parses arguments, validates ABI compatibility, performs the contract call using web3.py, and returns the decoded result wrapped in ToolResponse[ContractReadData]. Includes progress reporting and comprehensive error handling.
    @log_tool_invocation
    async def read_contract(
        chain_id: Annotated[str, Field(description="The ID of the blockchain")],
        address: Annotated[str, Field(description="Smart contract address")],
        abi: Annotated[
            dict[str, Any],
            Field(
                description=(
                    "The JSON ABI for the specific function being called. This should be "
                    "a dictionary that defines the function's name, inputs, and outputs. "
                    "The function ABI can be obtained using the `get_contract_abi` tool."
                )
            ),
        ],
        function_name: Annotated[
            str,
            Field(
                description=(
                    "The symbolic name of the function to be called. This must match the `name` field in the provided ABI."
                )
            ),
        ],
        args: Annotated[
            str,
            Field(
                description=(
                    "A JSON string containing an array of arguments. "
                    'Example: "["0xabc..."]" for a single address argument, or "[]" for no arguments. '
                    "Order and types must match ABI inputs. Addresses: use 0x-prefixed strings; "
                    'Numbers: prefer integers (not quoted); numeric strings like "1" are also '
                    "accepted and coerced to integers. "
                    "Bytes: keep as 0x-hex strings."
                )
            ),
        ] = "[]",
        block: Annotated[
            str | int,
            Field(
                description=(
                    "The block identifier to read the contract state from. Can be a block "
                    "number (e.g., 19000000) or a string tag (e.g., 'latest'). Defaults to 'latest'."
                )
            ),
        ] = "latest",
        *,
        ctx: Context,
    ) -> ToolResponse[ContractReadData]:
        """
            Calls a smart contract function (view/pure, or non-view/pure simulated via eth_call) and returns the
            decoded result.
    
            This tool provides a direct way to query the state of a smart contract.
    
            Example:
            To check the USDT balance of an address on Ethereum Mainnet, you would use the following arguments:
        {
          "tool_name": "read_contract",
          "params": {
            "chain_id": "1",
            "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
            "abi": {
              "constant": true,
              "inputs": [{"name": "_owner", "type": "address"}],
              "name": "balanceOf",
              "outputs": [{"name": "balance", "type": "uint256"}],
              "payable": false,
              "stateMutability": "view",
              "type": "function"
            },
            "function_name": "balanceOf",
            "args": "[\"0xF977814e90dA44bFA03b6295A0616a897441aceC\"]"
          }
        }
        """
        await report_and_log_progress(
            ctx,
            progress=0.0,
            total=2.0,
            message=f"Preparing contract call {function_name} on {address}...",
        )
    
        # Parse args from JSON string
        args_str = args.strip()
        if args_str == "":
            args_str = "[]"
        try:
            parsed = json.loads(args_str)
        except json.JSONDecodeError as exc:
            raise ValueError(
                '`args` must be a JSON array string (e.g., "["0x..."]"). Received a string that is not valid JSON.'
            ) from exc
        if not isinstance(parsed, list):
            raise ValueError(f"`args` must be a JSON array string representing a list; got {type(parsed).__name__}.")
        py_args = _convert_json_args(parsed)
    
        # Early arity validation for clearer feedback
        abi_inputs = abi.get("inputs", [])
        if isinstance(abi_inputs, list) and len(py_args) != len(abi_inputs):
            raise ValueError(f"Argument count mismatch: expected {len(abi_inputs)} per ABI, got {len(py_args)}.")
    
        # Normalize block if it is a decimal string
        if isinstance(block, str) and block.isdigit():
            block = int(block)
    
        def _for_check(a: Any) -> Any:
            if isinstance(a, list):
                return [_for_check(i) for i in a]
            if isinstance(a, str) and a.startswith(("0x", "0X")) and len(a) != 42:
                return decode_hex(a)
            return a
    
        check_args = [_for_check(a) for a in py_args]
        if not check_if_arguments_can_be_encoded(abi, *check_args):
            raise ValueError(f"Arguments {py_args} cannot be encoded for function '{function_name}'")
        w3 = await WEB3_POOL.get(chain_id)
        await report_and_log_progress(
            ctx,
            progress=1.0,
            total=2.0,
            message="Connected. Executing function call...",
        )
        contract = w3.eth.contract(address=to_checksum_address(address), abi=[abi])
        try:
            fn = contract.get_function_by_name(function_name)
        except ValueError as e:
            raise ValueError(f"Function name '{function_name}' is not found in provided ABI") from e
        try:
            result = await fn(*py_args).call(block_identifier=block)
        except ContractLogicError as e:
            raise RuntimeError(f"Contract call failed: {e}") from e
        except Exception as e:  # noqa: BLE001
            # Surface unexpected errors with context to the caller
            raise RuntimeError(f"Contract call errored: {type(e).__name__}: {e}") from e
        await report_and_log_progress(
            ctx,
            progress=2.0,
            total=2.0,
            message="Contract call successful.",
        )
        return build_tool_response(data=ContractReadData(result=result))
  • Registers the read_contract tool with the FastMCP server instance, setting structured_output=False and providing annotations with title 'Read from Contract'.
    mcp.tool(
        structured_output=False,
        annotations=create_tool_annotations("Read from Contract"),
    )(read_contract)
  • Pydantic BaseModel defining the output data structure for the read_contract tool, containing the 'result' field of type Any from the contract function call.
    class ContractReadData(BaseModel):
        """Result of a read-only smart contract function call."""
    
        result: Any = Field(description="Return value from the contract function call.")
  • Helper function to recursively convert JSON-parsed arguments into appropriate Python types for contract calls, including address checksum validation and numeric coercion.
    def _convert_json_args(obj: Any) -> Any:
        """
        Convert JSON-like arguments to proper Python types with deep recursion.
    
        - Recurses into lists and dicts
        - Attempts to apply EIP-55 checksum to address-like strings
        - Hex strings (0x...) remain as strings if not addresses
        - Numeric strings become integers
        - Other strings remain as strings
        """
        if isinstance(obj, list):
            return [_convert_json_args(item) for item in obj]
        if isinstance(obj, dict):
            return {k: _convert_json_args(v) for k, v in obj.items()}
        if isinstance(obj, str):
            try:
                return to_checksum_address(obj)
            except Exception:
                pass
            if obj.startswith(("0x", "0X")):
                return obj
            # Robust numeric detection: support negatives and large ints
            try:
                return int(obj, 10)
            except ValueError:
                return obj
        return obj
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=true and destructiveHint=false, which the description reinforces by emphasizing 'query' and 'read' operations. The description adds valuable context beyond annotations: it explains that both view/pure functions AND non-view/pure functions (via eth_call simulation) can be called, and mentions the tool returns 'decoded result' (helpful since there's no output schema). No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a clear purpose statement, usage context, and detailed example. The example is lengthy but necessary to demonstrate complex parameter interactions. Some sentences could be more concise, but overall it's efficiently organized with front-loaded key information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex tool with 6 parameters, 100% schema coverage, and no output schema, the description provides good context. It explains the tool's behavior, includes a comprehensive example showing parameter usage, and clarifies the return type ('decoded result'). It could mention error cases or limitations, but covers the essential functionality well.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 6 parameters thoroughly. The description doesn't add significant parameter semantics beyond what's in the schema descriptions, though the example illustrates how parameters work together. Baseline 3 is appropriate when schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Calls a smart contract function (view/pure, or non-view/pure simulated via eth_call) and returns the decoded result.' It specifies the verb ('calls'), resource ('smart contract function'), and scope ('view/pure' functions or simulated calls). It distinguishes from siblings by focusing on contract state queries rather than transactions, blocks, or token lookups.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context: 'This tool provides a direct way to query the state of a smart contract.' It implies usage for read-only contract interactions, but doesn't explicitly state when NOT to use it or name specific alternatives. The example shows a balance check, which helps illustrate appropriate use cases.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/blockscout/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server