MCP Explained Part 2: Optimizations
The Blockscout MCP server optimizes blockchain data for LLM agents by reprocessing API responses to save context and improve accuracy. Through truncation, simplification, and context-aware pagination, it delivers faster, more precise, and efficient AI interactions with onchain data.
TL;DR: The Blockscout MCP server reprocesses Blockscout API responses to make them suitable for LLM agents. Its main goal is to save context and improve the accuracy of blockchain data analysis.
To achieve this, the server applies several optimizations: it reduces response size (truncations), simplifies complex structures (simplifications), implements context-aware pagination, and separates smart contract handling into distinct phases (ABI and source code).
These techniques let agents receive only the relevant fragments of data while preserving the option to fully restore them when needed. The result is more precise, faster, and more efficient AI interaction with blockchain data.
MCP Explained - Index
Problem Statement
The Blockscout API was originally designed as a data source for the Blockscout UI. As a result, many endpoint responses are optimized for interface needs: they have rich structures, include repeating fragments, and aggregate parts of the data to reduce the number of network requests and speed up page rendering in the browser.
Historically, the second group of consumers were external services and scripts. For them, this format is acceptable - the response sizes are relatively moderate, and clients can easily filter and transform JSON into their own data structures, ignoring unnecessary fields without significant overhead.
With the introduction of AI agents, the situation changes.
An LLM (the model that controls the agent) consumes responses in their entirety as context. It can later “focus” on relevant fields, but by that time, tokens have already been spent: large and redundant responses directly reduce the available context budget and increase the cost of the query. Therefore, using Blockscout API responses “as is” at the MCP server level is inefficient.
Solution
The MCP server should act as an adapter between the Blockscout API and the LLM: it must handle context carefully and pass on only the minimally necessary and informative subset of data. This approach is confirmed in Anthropic's article "Writing effective tools for agents - with agents."
Data Truncations
Most Blockscout APIs return large amounts of data - long hex strings, nested structures, and sizable text fields. For an LLM, such data is not only redundant but also quickly consumes context. To avoid this, the MCP server applies truncation techniques, keeping only the meaningful part of the information.
Context-Aware Data Simplification
Some responses from the Blockscout API contain detailed structures with dozens of fields. They were originally designed for the Blockscout UI, which aims to reduce the number of API and database calls to minimize page rendering delays.
For an LLM, such optimization makes no sense: API response time is not critical, and interaction speed is determined by the model’s own generation and analysis process. What matters far more is the amount of data loaded into the context. Therefore, the MCP server simplifies responses - removing fields that provide no analytical value and only overload the model.
For example, when retrieving a list of ERC-20 tokens for an address, visual and marketing fields like icon_url, volume_24h, and type (the same across all entries) are excluded. For NFT collections, fields such as animation_url, media_url, and base64-encoded image metadata are removed.
This way, the LLM receives contextually meaningful and lightweight responses that are sufficient for analysis without unnecessary UI-oriented details.
Address Object Simplification
In many Blockscout API endpoints, fields such as from and to are returned as full address objects. This structure includes numerous additional properties (for example, contract name, tags, or verification flags). When the response contains a list of entities, these objects are duplicated over and over again:
- in transaction and transfer lists, the same
fromortofield may appear dozens of times; - in log lists, the contract address that emitted the event is also repeated for every entry.
The example below shows two transaction objects where the from and to fields are completely identical in structure, even though only the address hash is necessary to understand the meaning.
{
"timestamp": "2025-05-08T21:52:23.000000Z",
"total": {
"decimals": "6",
"value": "120793153368"
},
"type": "ERC-20",
"value": null,
"hash": "0x4a791106ecfb1913288f59da47f08fd22b9eaba47eff1d52f84feb8b8dc8ddf5",
"from": {
"ens_domain_name": null,
"hash": "0x9008D19f58AAbD9eD0D60971565AA8510560ab41",
"implementations": [],
"is_contract": true,
"is_scam": false,
"is_verified": true,
"metadata": null,
"name": "GPv2Settlement",
"private_tags": [],
"proxy_type": null,
"public_tags": [],
"reputation": "ok",
"watchlist_names": []
},
"token": {
"address_hash": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"circulating_market_cap": "73195789266.01447",
"decimals": "6",
"exchange_rate": "0.999738",
"holders_count": "4325092",
"icon_url": "<https://assets.coingecko.com/coins/images/6319/small/usdc.png?1696506694>",
"name": "USDC",
"reputation": "ok",
"symbol": "USDC",
"total_supply": "48089335743630657",
"type": "ERC-20",
"volume_24h": "14466353521.3286"
},
"to": {
"ens_domain_name": null,
"hash": "0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7",
"implementations": [],
"is_contract": true,
"is_scam": false,
"is_verified": true,
"metadata": null,
"name": "TimelockController",
"private_tags": [],
"proxy_type": null,
"public_tags": [],
"reputation": "ok",
"watchlist_names": []
},
"method": "MoooZ1089603480",
"block_number": 22441590,
"transaction_index": 30,
"fee": "7147718081316028",
"reputation": "ok",
"internal_transaction_index": null,
"token_transfer_batch_index": 1,
"token_transfer_index": 101,
"created_contract": null
},
{
"timestamp": "2025-05-08T15:42:59.000000Z",
"total": {
"decimals": "6",
"value": "114729659643"
},
"type": "ERC-20",
"value": null,
"hash": "0xa3a524bf8006d108fcf3480fd933659750eddc6184f46e0ce0b59ea7724f117f",
"from": {
"ens_domain_name": null,
"hash": "0x9008D19f58AAbD9eD0D60971565AA8510560ab41",
"implementations": [],
"is_contract": true,
"is_scam": false,
"is_verified": true,
"metadata": null,
"name": "GPv2Settlement",
"private_tags": [],
"proxy_type": null,
"public_tags": [],
"reputation": "ok",
"watchlist_names": []
},
"token": {
"address_hash": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"circulating_market_cap": "73195789266.01447",
"decimals": "6",
"exchange_rate": "0.999738",
"holders_count": "4325092",
"icon_url": "<https://assets.coingecko.com/coins/images/6319/small/usdc.png?1696506694>",
"name": "USDC",
"reputation": "ok",
"symbol": "USDC",
"total_supply": "48089335743630657",
"type": "ERC-20",
"volume_24h": "14466353521.3286"
},
"to": {
"ens_domain_name": null,
"hash": "0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7",
"implementations": [],
"is_contract": true,
"is_scam": false,
"is_verified": true,
"metadata": null,
"name": "TimelockController",
"private_tags": [],
"proxy_type": null,
"public_tags": [],
"reputation": "ok",
"watchlist_names": []
},
"method": "MoooZ1089603480",
"block_number": 22439767,
"transaction_index": 94,
"fee": "22216028115612040",
"reputation": "ok",
"internal_transaction_index": null,
"token_transfer_batch_index": 1,
"token_transfer_index": 743,
"created_contract": null
},
For LLM purposes, such redundancy has no value. It consumes context and complicates further processing. It is more efficient to return only the address hash instead of the full object. If the agent needs additional details about the address, it can always make a separate request.
Transaction Input Data Truncation
The API response that returns transaction info contains at least the raw_input field. For transactions interacting with verified contracts, there is also a decoded_input field with the decoded call parameters.
{
. . .,
"raw_input": "0x6a76120200000000000000000000000040a2accbd92bca938b02010e17a5b8929b49130d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000002248d80ff0a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001cb00b1377e4f32e6746444970823d5506f98f5a0420100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044aad3ec96000000000000000000000000690f0581ececcf8389c223170778cd9d029606f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00d7a029db2585553978190db5e85ec724aa4df23f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044aad3ec96000000000000000000000000690f0581ececcf8389c223170778cd9d029606f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0005c8f60e24fcdd9b8ed7bb85df8164c41cb4da1600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044aad3ec96000000000000000000000000690f0581ececcf8389c223170778cd9d029606f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c36f1b74030343c54772cb4cd5d954c9116e2dcc497cc4c4671849ece016ca9c02602f62a0e63a5c4d473287384e5afe82a0c6742140b01231a427b73d08de54c91cbb9c241fd3a74a73dbcf7cd9089285dd4b5cea09665fc8ef5489e8dabe1e6fa430c64ce67b61c5641ca53c0b727d8e1a6f6ef06e231d46d3451b20e7af21ff351c4019ea37dce0bd780078afa92593e9202f06a63f09f76c23a79a0eadb2bc6a7f44a747746fc7f56aea1f557e9d893d4ac5c01e81102df630a9e51f9c0e8f23c21b0000000000000000000000000000000000000000000000000000000000",
. . .,
"decoded_input": {
"method_call": "execTransaction(address to, uint256 value, bytes data, uint8 operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, bytes signatures)",
"method_id": "6a761202",
"parameters": [
{
"name": "to",
"type": "address",
"value": "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D"
},
{
"name": "value",
"type": "uint256",
"value": "0"
},
{
"name": "data",
"type": "bytes",
"value": "0x8d80ff0a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001cb00b1377e4f32e6746444970823d5506f98f5a0420100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044aad3ec96000000000000000000000000690f0581ececcf8389c223170778cd9d029606f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00d7a029db2585553978190db5e85ec724aa4df23f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044aad3ec96000000000000000000000000690f0581ececcf8389c223170778cd9d029606f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0005c8f60e24fcdd9b8ed7bb85df8164c41cb4da1600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044aad3ec96000000000000000000000000690f0581ececcf8389c223170778cd9d029606f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000"
},
{
"name": "operation",
"type": "uint8",
"value": "1"
},
{
"name": "safeTxGas",
"type": "uint256",
"value": "0"
},
{
"name": "baseGas",
"type": "uint256",
"value": "0"
},
{
"name": "gasPrice",
"type": "uint256",
"value": "0"
},
{
"name": "gasToken",
"type": "address",
"value": "0x0000000000000000000000000000000000000000"
},
{
"name": "refundReceiver",
"type": "address",
"value": "0x0000000000000000000000000000000000000000"
},
{
"name": "signatures",
"type": "bytes",
"value": "0x6f1b74030343c54772cb4cd5d954c9116e2dcc497cc4c4671849ece016ca9c02602f62a0e63a5c4d473287384e5afe82a0c6742140b01231a427b73d08de54c91cbb9c241fd3a74a73dbcf7cd9089285dd4b5cea09665fc8ef5489e8dabe1e6fa430c64ce67b61c5641ca53c0b727d8e1a6f6ef06e231d46d3451b20e7af21ff351c4019ea37dce0bd780078afa92593e9202f06a63f09f76c23a79a0eadb2bc6a7f44a747746fc7f56aea1f557e9d893d4ac5c01e81102df630a9e51f9c0e8f23c21b"
}
]
},
. . .
}
In blockchain, some transactions (especially contract creation) have raw_input fields that take up hundreds or even thousands of bytes. In theory, an LLM could parse these raw bytes, but in practice, this is rarely necessary and heavily consumes context. The first 4 bytes of raw_input are the method selector, which is often useful for filtering.
The decoded_input fields that contain large blobs (bytes, string) also tend not to be meaningfully analyzed by the model but significantly increase context size. It is therefore reasonable to keep only a small prefix and mark the value as truncated.
As a result, context usage is optimized by shortening raw_input and the parameter values in decoded_input whenever their size exceeds a given threshold (for example, keeping only the first 256 bytes). The response also includes explicit truncation indicators, instructing the LLM that the data has been modified.
Log Data Field Truncation
The API response that provides information about transaction logs or all logs emitted by a specific contract also contains raw data in the data field accompanying each log.
{
. . .,
"data": "0x4d414e41474552000000000000000000000000000000000000000000000000000000000000000000000000008164cc65827dcfe994ab23944cbc90e0aa80bfcb236300dc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000",
"decoded": {
"method_call": "ScopeFunction(bytes32 roleKey, address targetAddress, bytes4 selector, (uint8,uint8,uint8,bytes)[] conditions, uint8 options)",
"method_id": "4f6c3404",
"parameters": [
{
"indexed": false,
"name": "roleKey",
"type": "bytes32",
"value": "0x4d414e4147455200000000000000000000000000000000000000000000000000"
},
{
"indexed": false,
"name": "targetAddress",
"type": "address",
"value": "0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb"
},
{
"indexed": false,
"name": "selector",
"type": "bytes4",
"value": "0x236300dc"
},
{
"indexed": false,
"name": "conditions",
"type": "(uint8,uint8,uint8,bytes)[]",
"value": [
[
"0",
"5",
"5",
"0x"
],
[
"0",
"4",
"0",
"0x"
],
[
"0",
"1",
"0",
"0x"
],
[
"0",
"1",
"15",
"0x"
],
[
"1",
"1",
"0",
"0x"
]
]
},
{
"indexed": false,
"name": "options",
"type": "uint8",
"value": "0"
}
]
},
. . .
},
If the log is emitted by a verified contract, the decoded data is located in the decoded structure.
Since large blobs are not meant to be analyzed by the model, the data field and the corresponding parameters inside decoded are shortened in the same way as transaction data in the previous section.
Constructor Arguments Truncation
The API endpoint that returns information about a deployed contract includes the fields constructor_args and decoded_constructor_args.
Example:
{
...,
"decoded_constructor_args": [
[
"1",
{
"internalType": "uint256",
"name": "chainId_",
"type": "uint256"
}
]
],
. . .,
"constructor_args": "0x0000000000000000000000000000000000000000000000000000000000000001"
}
If these fields contain overly long encoded constructor arguments (such as arrays, large strings, or big byte data), they are shortened in the same way as input data and log fields, since in most cases the LLM will not extract any useful information from them.
Pagination
Most Blockscout APIs return data in pages, since the number of transactions, tokens, or logs can reach thousands and cannot be transferred in a single response. Without pagination support, models would often receive incomplete data - for example, when a transaction has too many logs or an address holds numerous tokens.
Therefore, the Blockscout MCP server implements its own pagination handling, allowing LLMs to retrieve all data sequentially while keeping the process simple and efficient in terms of context usage.
Opaque Cursor Strategy
The Blockscout API uses different pagination schemes depending on the type of data. One endpoint may rely on index and block_number, another on transaction_index and token_transfer_index, and yet another on id, value, or even fiat_value.
For example:
{
"next_page_params": {
"index": 60,
"block_number": 18298489,
"items_count": 50
}
}
{
"next_page_params": {
"block_number": 21795378,
"transaction_index": 221,
"internal_transaction_index": null,
"token_transfer_batch_index": 1,
"token_transfer_index": 447,
"items_count": 50
}
}
{
"next_page_params": {
"id": 8891370227,
"value": "10000000000000000",
"fiat_value": "700.0000000000000000",
"items_count": 50
}
}
Each parameter set is unique and depends on the logic of the specific endpoint. For an LLM, such diversity creates problems: the model may make mistakes when copying parameters, mix up their order, or even try to modify them while attempting to “control” pagination without understanding the internal meaning of these fields.
Adding descriptions of all these parameters to every MCP tool’s description is also impractical - it would dramatically increase the text size and, consequently, the LLM’s context consumption.
To address this, the MCP server uses an opaque cursor strategy. All parameters returned in next_page_params are combined into a single structure, serialized into compact JSON, and encoded in Base64URL. The LLM receives only one parameter, cursor, without knowing its internal contents.
Example: if the Blockscout API returns
"next_page_params": { "block_number": 21795378, "transaction_index": 221, "items_count": 50 }
the server transforms it into an encoded string:
eyJibG9ja19udW1iZXIiOjIxNzk1Mzc4LCJ0cmFuc2FjdGlvbl9pbmRleCI6MjIxLCJpdGVtc19jb3VudCI6NTB9
This approach makes pagination:
- universal - the same format is used for all tools;
- safe - the model cannot accidentally corrupt parameters;
- efficient - a single cursor parameter description consumes minimal context instead of repeating unique parameter sets for every tool.
So, the opaque cursor strategy solves three problems at once: it preserves compatibility with different Blockscout APIs, simplifies tool structure, and minimizes LLM context usage.
Response slicing
Some responses from the Blockscout API contain dozens of items (usually up to 50), but for analysis, the model often needs only the most extreme entries - for example, the newest ones or those with the largest amounts.In such cases, most of the data is unnecessary and only consumes the model’s context. To avoid this, when the MCP server receives a full page from Blockscout, it returns only a small slice - for instance, the first 10 items.
If there is more data than the slice size, the server generates its own continuation cursor. It is calculated based on the last element of the returned block, ensuring the list continues correctly in the next request.
When the server applies filters and too few items remain after filtering, it can fetch several pages in a row and accumulate the results until a sufficient sample is collected. This prevents cases where the model would receive very short or even “empty” responses.
This approach keeps the context compact while still giving the model access to all data - sequentially, through controlled pagination.
Context-Aware Pagination
After the server returns a shortened data slice, the next challenge is to correctly continue pagination on the following step. Simply using the pagination parameters provided by the Blockscout API does not work here - they refer to the continuation relative to the original 50 items, not to the 10 that were actually returned to the model.
To solve this, the MCP server generates a context-aware cursor. It is calculated based on the last element of the returned slice rather than the full dataset received from Blockscout.
This cursor includes the parameters Blockscout uses to order data - for example, block number, transaction index, or token contract hash - ensuring precise continuation of the sequence. The cursor is then serialized into JSON and encoded in Base64URL, becoming a single compact cursor parameter.
For example, for the /api/v2/advanced-filters endpoint, where the API returns next_page_params like:
{
"block_number": 21795378,
"transaction_index": 221,
"internal_transaction_index": null,
"token_transfer_batch_index": 1,
"token_transfer_index": 447,
"items_count": 50
}
the server creates a new cursor based on the block_number, transaction_index, internal_transaction_index, token_transfer_batch_index, and token_transfer_index fields of the last element among the first ten returned items.
Similarly, for /api/v2/addresses/{address}/nft/collections, where next_page_params contains:
{
"token_type": "ERC-721",
"token_contract_address_hash": "0x858df9f84c73e01c55a2dfb95825401242a65d64",
"items_count": 50
}
the new cursor will include the token_type and token_contract_address_hash of the last returned record.
This approach makes pagination context-aware - it relies on the actual data provided to the model rather than the structure of the original API response. As a result, the server preserves the exact sequence of data and avoids missing items between pages, even when the slice size is smaller than the standard Blockscout page size.
Automatic Pagination Instructions
LLM often ignore structured pagination fields, even when the server provides them in a convenient format. They tend to “see” only the main data content, overlooking the fact that the dataset is incomplete and can be continued. As a result, request chains get interrupted, and the final analysis remains partial.
To prevent this, the MCP server automatically adds textual instructions to every response where pagination is available, motivating the model to continue fetching data.
The instructions field includes clear phrases like “⚠️ MORE DATA AVAILABLE: Use pagination.next_call to get the next page.” These are perceived not as technical details but as explicit actions the model should take.
These messages duplicate the structured pagination field, which already contains a ready-to-use example of the next call. This creates a dual-layer navigation system: the LLM receives both the exact parameters for continuation and an explicit textual cue that more data can be retrieved.
This approach was discovered experimentally: in early versions of the MCP server, models often ignored pagination. However, adding explicit instructions and notices to MCP tool descriptions led agents to follow pagination links consistently - continuing through pages as long as it made sense for the analysis.
Smart Contracts
Since most interactions with the blockchain occur through smart contracts, the MCP server must provide a way to analyze them. However, the main Blockscout endpoint, /api/v2/smart-contracts/{address}, returns too much information for direct use by an LLM.
To make such analysis efficient, the MCP server applies simplified contract representations and a careful division of functionality - retrieving the ABI, selectively reading source files, and truncating redundant fields. This approach allows models to understand the structure and behavior of contracts without losing context or processing excessive data.
Contract Source Code and ABI Separation
A Blockscout API response for a contract usually includes everything at once: source code, ABI, compiler parameters, optimization settings, and links to additional files. This amount of data can reach tens of kilobytes, quickly consuming the LLM’s context. However, for most analytical tasks, the ABI alone is sufficient without the source code.
Example of a typical (shortened) Blockscout API response:
{
"name": "LidoExecutionLayerRewardsVault",
"language": "solidity",
"file_path": "contracts/0.8.9/LidoExecutionLayerRewardsVault.sol",
"source_code": "// SPDX-FileCopyrightText: 2021 Lido <info@lido.fi>...",
"additional_sources": [
{
"file_path": "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol",
"source_code": "// SPDX-License-Identifier: MIT..."
},
{
"file_path": "@openzeppelin/contracts-v4.4/utils/Address.sol",
"source_code": "// SPDX-License-Identifier: MIT..."
}
],
"abi": [
{
"type": "function",
"name": "recoverERC20",
"inputs": [
{"name": "_token", "type": "address"},
{"name": "_amount", "type": "uint256"}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "LIDO",
"inputs": [],
"outputs": [{"type": "address"}],
"stateMutability": "view"
}
]
}
Such a response is inconvenient to send directly into the model’s context since it contains both the source code and the interface structure. Yet for most analytical scenarios, the source code is unnecessary. The ABI alone, which defines the contract’s methods, events, and data types, is enough.
Typical cases where the ABI is sufficient include:
- reading contract state - for
eth_call, it’s enough to know the method signature and arguments; - basic understanding of contract functionality - the list of functions and events provides a good overview without the sources;
- analyzing transactions with a
raw_inputfield - using the ABI, the data can be parsed to identify the called method and arguments; - analyzing logs - the ABI allows computing the event signature (
topic0) and matching it with an event; - comparing contracts - the ABI structure helps determine whether two contracts implement the same interfaces.
Therefore, the MCP server separates the ABI from the source code. The tool that returns the ABI is intended for most analytical operations, while the sources are handled by other tools. This separation helps preserve the LLM’s context and speeds up contract data processing without sacrificing analytical capabilities.
Two-Phase Source Code Inspection
Smart contract source code can be large and consist of dozens of files. Sending the entire code at once is impractical - it would instantly exhaust the LLM’s context.
Therefore, the MCP server implements a two-phase approach to source code analysis, allowing the model to first get an overview of the contract and then load only the necessary fragments.
Phase 1 – Overview
In the first step, the tool returns the contract’s metadata and the structure of its source files, but without their contents. This gives the model an understanding of the project composition - which files exist, how they are named, and which ones are likely to contain the core logic. This way, the LLM can decide what to explore further without wasting context on secondary libraries or interfaces.
Phase 2 – Selective
After obtaining the structure, the model can request individual files. The MCP server returns only the selected parts. This approach is especially useful when studying large contracts, where the main interest lies in a few key files rather than the entire codebase.
Dividing the analysis into two phases preserves flexibility and makes working with contracts more manageable: the LLM can explore the code gradually, like a developer who first opens the project and then looks into specific files.