Skip to content

Conversation

akolotov
Copy link
Collaborator

@akolotov akolotov commented Aug 27, 2025

Summary

  • drop invalid limit query param from Arbitrum messages and blocks-validated integration tests
  • ensure pagination logic uses default server page sizes

Testing

  • ruff check .
  • ruff format --check .
  • pytest
  • pytest -m integration -v (fails: contract call runtime errors)

Closes #215


https://chatgpt.com/codex/tasks/task_b_68ae9cdda01c83238ea708381dfe2f3e

Summary by CodeRabbit

  • New Features

    • Added direct_api_call tool for curated Blockscout endpoints with cursor pagination; exposed via REST, UI, MCP instructions, and surfaced as actionable guidance in address/transaction responses.
  • Documentation

    • Added docs, spec, examples, OpenAPI, and GPT instruction entries for direct_api_call across README, API/SPEC, TESTING, and tool descriptions.
  • Models

    • Added schema for direct API endpoints and response container; Instructions payload extended to include rules and endpoint list.
  • Tests

    • Added unit, route, and integration tests for params, pagination, errors, and instruction propagation (some duplicated tests).
  • Chores

    • Bumped project and extension manifest versions.

Copy link

coderabbitai bot commented Aug 27, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a new direct_api_call tool (implementation, models, constants), exposes it via a REST route, injects curated endpoint rules into initialization output, augments address/transaction tools with follow-up instructions, updates docs/manifests/OpenAPI/templates, and adds unit, integration, and route tests plus version bumps.

Changes

Cohort / File(s) Summary
New tool implementation
blockscout_mcp_server/tools/direct_api_tools.py
Adds async direct_api_call(...): resolves base URL, validates endpoint_path, applies cursor to params, calls make_blockscout_request, constructs pagination NextCallInfo/PaginationInfo, validates response into DirectApiData, and returns ToolResponse[DirectApiData].
API routing / REST wrapper
blockscout_mcp_server/api/routes.py
Adds direct_api_call_rest registered at /v1/direct_api_call: validates chain_id/endpoint_path, collects remaining query params into query_params, calls tool with ctx, returns JSON; decorated with @handle_rest_errors.
Server integration & MCP instructions
blockscout_mcp_server/server.py
Imports DIRECT_API_CALL_*, registers direct_api_call as an MCP tool, adds format_endpoint_groups, and injects formatted direct_api_call rules/endpoints into MCP instructions payload.
Models & constants
blockscout_mcp_server/models.py, blockscout_mcp_server/constants.py
Adds DirectApiData, DirectApiEndpoint, DirectApiCommonGroup, DirectApiSpecificGroup, DirectApiEndpointList; extends InstructionsData with direct_api_call_rules and direct_api_endpoints; adds DIRECT_API_CALL_RULES and DIRECT_API_CALL_ENDPOINT_LIST.
Initialization wiring
blockscout_mcp_server/tools/initialization_tools.py
Builds DirectApiEndpointList from constants and injects direct_api_call_rules and direct_api_endpoints into __unlock_blockchain_analysis__ response.
Contextual instructions in tools
blockscout_mcp_server/tools/address_tools.py, blockscout_mcp_server/tools/transaction_tools.py
Adds instructions guidance recommending direct_api_call follow-ups; tool responses now include instructions.
Tool response schema / common
blockscout_mcp_server/tools/common
build_tool_response updated to accept optional instructions (list[str]); ToolResponse exposes instructions.
Docs, OpenAPI, UI, LLM & manifests
SPEC.md, README.md, API.md, AGENTS.md, blockscout_mcp_server/llms.txt, blockscout_mcp_server/templates/index.html, gpt/action_tool_descriptions.md, gpt/instructions.md, gpt/openapi.yaml, TESTING.md, dxt/manifest.json, dxt/manifest-dev.json
Add documentation and OpenAPI entry for the new tool and discovery data; add tool entry to manifests; add curl example; bump manifest versions.
Version bumps
pyproject.toml, blockscout_mcp_server/__init__.py
Project and package version incremented to 0.10.0.dev1.
Tests — unit, integration, route, models
tests/tools/test_direct_api_tools.py, tests/integration/test_direct_api_tools_integration.py, tests/api/test_routes.py, tests/test_models.py, tests/tools/test_initialization_tools.py, tests/tools/test_address_tools.py, tests/tools/test_transaction_tools_2.py
Adds unit tests for direct_api_call (params, cursor, pagination, error), integration pagination tests, REST route tests (including required-parameter checks), and updates tests to cover new models/fields (instructions, direct_api_call_rules, direct_api_endpoints).

Sequence Diagram(s)

%%{init: {"themeVariables":{"actorBorder":"#2b6cb0","actorFill":"#e6f2ff","noteBorder":"#a0aec0","noteText":"#2d3748"}} }%%
sequenceDiagram
  autonumber
  participant Client as Client
  participant REST as REST Router (/v1/direct_api_call)
  participant Tool as direct_api_call
  participant BS as Blockscout API

  Client->>REST: GET /v1/direct_api_call?chain_id=&endpoint_path=&cursor?&query_params...
  REST->>REST: Validate chain_id & endpoint_path
  REST->>Tool: direct_api_call(chain_id, endpoint_path, query_params, cursor, ctx)
  Tool->>Tool: validate path → apply cursor → merge params
  Tool->>BS: make_blockscout_request(base_url, endpoint_path, params)
  BS-->>Tool: JSON response (+ next_page_params?)
  Tool->>Tool: build next_cursor & NextCallInfo (if paginated)
  Tool-->>REST: ToolResponse {data, pagination?}
  REST-->>Client: 200 JSON ToolResponse
Loading
%%{init: {"themeVariables":{"actorBorder":"#2b6cb0","actorFill":"#e6f2ff","noteBorder":"#a0aec0","noteText":"#2d3748"}} }%%
sequenceDiagram
  autonumber
  participant AI as AI/Agent
  participant Init as __unlock_blockchain_analysis__
  participant Cfg as Constants (DIRECT_API_CALL_*)
  participant MCP as MCP Server

  AI->>Init: request unlock
  Init->>Cfg: read DIRECT_API_CALL_RULES & ENDPOINT_LIST
  Init-->>AI: InstructionsData {direct_api_call_rules, direct_api_endpoints, ...}
  note over AI,MCP: Tools may include contextual `instructions` suggesting `direct_api_call`
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
Implement direct_api_call tool with signature, pagination, use of make_blockscout_request, and server registration [#215]
Provide curated endpoint rules/list via constants and include in __unlock_blockchain_analysis__ [#215]
Add contextual instructions in get_address_info and get_transaction_info [#215]
Expose REST route and update OpenAPI/docs/manifests/UI/LLM materials [#215]

Out-of-scope changes (observations)

Code Change Explanation
Duplicate test blocks in tests/api/test_routes.py (two identical groups of four tests) Appears to be accidental duplicate test insertion; not required by linked issue objectives and increases test redundancy.
Duplicate direct_api_call section in SPEC.md The same documentation content appears twice; likely an accidental duplication unrelated to functional objectives.

Possibly related PRs


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 171ee10 and 6825899.

📒 Files selected for processing (1)
  • tests/tools/test_direct_api_tools.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/tools/test_direct_api_tools.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Docker build and docker push
  • GitHub Check: Run Integration Tests
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/implement-direct_api_call-tool

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/tools/test_direct_api_tools.py (2)

103-109: Tighten pagination assertions and verify single request + progress

Also assert chain_id/endpoint_path are carried in next_call and that mocks were called once.

Apply:

         result = await direct_api_call(chain_id=chain_id, endpoint_path=endpoint_path, ctx=mock_ctx)

         assert isinstance(result, ToolResponse)
         assert result.pagination is not None
         assert result.pagination.next_call.tool_name == "direct_api_call"
-        assert "cursor" in result.pagination.next_call.params
+        nc = result.pagination.next_call.params
+        assert nc["chain_id"] == chain_id
+        assert nc["endpoint_path"] == endpoint_path
+        assert "cursor" in nc
+        # No query_params in next_call when none were provided
+        assert "query_params" not in nc
+        # Single network call, and 3 progress updates
+        mock_get_url.assert_called_once_with(chain_id)
+        mock_request.assert_called_once_with(base_url=mock_base_url, api_path=endpoint_path, params={})
+        assert mock_ctx.report_progress.await_count == 3

1-109: Add error-path unit test (timeout/exception propagation)

Per guidelines, include error cases. Verify exceptions are raised (not wrapped into ToolResponse).

Add this test:

@@
 @pytest.mark.asyncio
 async def test_direct_api_call_with_pagination(mock_ctx):
@@
         assert "cursor" in result.pagination.next_call.params
+
+@pytest.mark.asyncio
+async def test_direct_api_call_raises_on_request_error(mock_ctx):
+    chain_id = "1"
+    endpoint_path = "/api/v2/data"
+    mock_base_url = "https://eth.blockscout.com"
+    with (
+        patch("blockscout_mcp_server.tools.direct_api_tools.get_blockscout_base_url", new_callable=AsyncMock) as mock_get_url,
+        patch("blockscout_mcp_server.tools.direct_api_tools.make_blockscout_request", new_callable=AsyncMock) as mock_request,
+    ):
+        mock_get_url.return_value = mock_base_url
+        mock_request.side_effect = TimeoutError("upstream timeout")
+        with pytest.raises(TimeoutError):
+            await direct_api_call(chain_id=chain_id, endpoint_path=endpoint_path, ctx=mock_ctx)
+        mock_get_url.assert_called_once_with(chain_id)
+        mock_request.assert_awaited_once()
+        # Even on error, two progress reports should have occurred (resolve URL, start fetch)
+        assert mock_ctx.report_progress.await_count >= 2
🧹 Nitpick comments (21)
blockscout_mcp_server/llms.txt (1)

62-62: Add a brief note to steer pagination usage (avoid unsupported limit).

Keeps agent guidance aligned with tests removing limit.

Apply this diff:

-18. **`direct_api_call`** - Calls a curated raw Blockscout API endpoint
+18. **`direct_api_call`** - Calls a curated raw Blockscout API endpoint. Do not pass a `limit` parameter; page size is set by the server—advance using the returned `cursor`.
API.md (3)

497-504: Fix markdownlint violation by using real subheadings; keep param table intact

Replace emphasized labels with proper H4 headings to satisfy MD036; table content is fine.

-**Parameters**
+#### Parameters
@@
-| Name | Type | Required | Description |
-| ---- | ---- | -------- | ----------- |
-| `chain_id` | `string` | Yes | The ID of the blockchain. |
-| `endpoint_path` | `string` | Yes | The Blockscout API path to call (e.g., `/api/v2/stats`). |
-| `cursor` | `string` | No | The cursor for pagination from a previous response. |
+| Name          | Type     | Required | Description                                              |
+| ------------- | -------- | -------- | -------------------------------------------------------- |
+| `chain_id`    | `string` | Yes      | The ID of the blockchain.                                |
+| `endpoint_path` | `string` | Yes    | The Blockscout API path to call (e.g., `/api/v2/stats`). |
+| `cursor`      | `string` | No       | The cursor for pagination from a previous response.      |

505-506: Remove parameter-behavior prose to keep API.md concise per house style

API.md should be brief and avoid explaining parameter behavior. Consider moving this note to TESTING.md or SPEC.md.

-Any additional query parameters appended to the URL are forwarded directly to the Blockscout API.
+<!-- Additional query parameters are supported; see TESTING.md for examples. -->

507-511: Include an example showing the optional cursor; convert label to a proper heading

Add a second curl with cursor to demonstrate pagination, and convert the label to H4 heading.

-**Example Request**
+#### Example Request
@@
 ```bash
 curl "http://127.0.0.1:8000/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/stats"

+#### Example Request (with pagination cursor)
+
+bash +curl "http://127.0.0.1:8000/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/stats&cursor=eyJwYWdlIjoyfQ==" +


</blockquote></details>
<details>
<summary>TESTING.md (1)</summary><blockquote>

`230-235`: **Augment the Direct API Call example with forwarded params and pagination**

Demonstrate a realistic call with extra query params and a cursor to mirror production usage.


```diff
 #### 6. Direct API Call
 
 ```bash
-curl "http://127.0.0.1:8000/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/stats"
+curl "http://127.0.0.1:8000/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/tokens/0xdAC17F958D2ee523a2206206994597C13D831ec7/holders&age_from=2024-01-01T00:00:00Z"
+
+# Example: next page using the returned cursor (value shown here is illustrative)
+curl "http://127.0.0.1:8000/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/tokens/0xdAC17F958D2ee523a2206206994597C13D831ec7/holders&cursor=eyJwYWdlIjozfQ=="

</blockquote></details>
<details>
<summary>gpt/instructions.md (2)</summary><blockquote>

`78-141`: **Validate endpoint path accuracy and placeholders**

Some paths are easy to mistype (e.g., placeholder names or singular/plural segments). Please verify each against the live API or the constants source.


Would you like me to open a follow-up to add a small schema (name, path template, brief description) and auto-render this section from the constants to eliminate manual edits?

---

`59-76`: **Sync direct_api_call endpoint docs with code constants**  
Our diff script shows the static list in gpt/instructions.md under `<direct_call_endpoint_list>` is significantly out-of-sync with the `DIRECT_API_CALL_ENDPOINT_LIST` constants in the code. Many endpoints are missing, and stale descriptions remain. To prevent this drift going forward:

- Generate the markdown list directly from the source constants (e.g., a small build-time utility that emits the paths and descriptions).  
- Or add a CI check (using the above comparison script) to fail the build if `gpt/instructions.md` and the code constants diverge.  
- If you continue maintaining the list by hand, update it now to include all current endpoints and prune deprecated ones whenever code changes.

</blockquote></details>
<details>
<summary>AGENTS.md (1)</summary><blockquote>

`285-287`: **Keep tools list consistent with tests section.**

Please also list tests/integration/test_direct_api_tools_integration.py in the Integration Tests section for parity with other tools.

</blockquote></details>
<details>
<summary>blockscout_mcp_server/tools/direct_api_tools.py (2)</summary><blockquote>

`7-14`: **Use apply_cursor_to_params for cursor merging (consistency with other tools).**

Centralize cursor handling via the shared helper. 

Apply this diff:

```diff
 from blockscout_mcp_server.tools.common import (
     build_tool_response,
-    decode_cursor,
     encode_cursor,
     get_blockscout_base_url,
     make_blockscout_request,
     report_and_log_progress,
+    apply_cursor_to_params,
 )
@@
-    params = dict(query_params) if query_params else {}
-    if cursor:
-        params.update(decode_cursor(cursor))
+    params = dict(query_params) if query_params else {}
+    apply_cursor_to_params(cursor, params)

Also applies to: 41-44


21-26: Clarify endpoint_path usage (no query string).

To avoid confusion and double-encoding, reject endpoint_path containing '?' and instruct callers to use query_params for all query args.

Apply this guard:

     endpoint_path: Annotated[str, Field(description="The Blockscout API path to call (e.g., '/api/v2/stats')")],
@@
 ) -> ToolResponse[dict[str, Any]]:
@@
-    base_url = await get_blockscout_base_url(chain_id)
+    base_url = await get_blockscout_base_url(chain_id)
+    if "?" in endpoint_path:
+        raise ValueError("Do not include query parameters in endpoint_path. Use query_params instead.")
README.md (1)

150-151: Add a short note about query_params vs query string.

To guide users, append “Pass all query parameters via query_params; do not append them to endpoint_path.”

tests/tools/test_transaction_tools_2.py (1)

82-87: Make assertion resilient to minor copy changes in instruction text

Match on the endpoint substring rather than the full sentence to avoid brittle failures if wording changes.

-        expected_instruction = (
-            "Use `direct_api_call` with endpoint "
-            f"`/api/v2/proxy/account-abstraction/operations?transaction_hash={hash}` "
-            "to get User Operations for this transaction."
-        )
-        assert result.instructions is not None and expected_instruction in result.instructions
+        endpoint_hint = f"/api/v2/proxy/account-abstraction/operations?transaction_hash={hash}"
+        assert result.instructions is not None
+        assert any(endpoint_hint in s for s in result.instructions)
tests/api/test_routes.py (1)

558-572: Avoid using 'limit' in direct_api_call tests; prefer a commonly supported param

To align with the PR goal of removing unsupported 'limit' usage, use a neutral key like page_size for the passthrough query param.

-    url = "/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/stats&limit=1"
+    url = "/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/stats&page_size=1"
@@
-        query_params={"limit": "1"},
+        query_params={"page_size": "1"},
blockscout_mcp_server/tools/transaction_tools.py (1)

548-556: Clarify instruction as ERC-4337-specific and shorten wording

This avoids implying all transactions have UserOps and keeps the hint crisp. If you adopt this, pair it with the test tweak suggested in tests/tools/test_transaction_tools_2.py.

-    instructions = [
-        (
-            "Use `direct_api_call` with endpoint "
-            f"`/api/v2/proxy/account-abstraction/operations?transaction_hash={transaction_hash}` "
-            "to get User Operations for this transaction."
-        )
-    ]
+    instructions = [
+        (
+            "To check for ERC-4337 User Operations related to this tx, call "
+            f"`direct_api_call` with endpoint "
+            f"`/api/v2/proxy/account-abstraction/operations?transaction_hash={transaction_hash}`."
+        )
+    ]
blockscout_mcp_server/api/routes.py (1)

268-279: Preserve multi-valued query params in REST wrapper.

dict(request.query_params) collapses repeated keys (e.g., topics=...&topics=...). Some Blockscout endpoints accept repeated params. Capture multi-values to lists and pass them through.

-    extra = dict(request.query_params)
-    for key in ["chain_id", "endpoint_path", "cursor"]:
-        extra.pop(key, None)
-    if extra:
-        params["query_params"] = dict(extra)
+    # Preserve multi-valued params (e.g., key=a&key=b -> {"key": ["a", "b"]})
+    ignored = {"chain_id", "endpoint_path", "cursor"}
+    extra: dict[str, Any] = {}
+    for key in request.query_params:
+        if key in ignored:
+            continue
+        values = request.query_params.getlist(key)
+        extra[key] = values if len(values) > 1 else values[0]
+    if extra:
+        params["query_params"] = extra
tests/tools/test_address_tools.py (1)

435-457: Verify endpoint path consistency for coin balance history.

One instruction uses /addresses/{address}/coin-balance-history (no /api/v2). Other instructions use /api/v2/.... Confirm the actual endpoint path exposed by Blockscout and align the expected string. Consider accepting either variant to reduce brittleness.

Example (accept either path in assertion):

-        expected_instructions = [
+        expected_instructions = [
             (f"Use `direct_api_call` with endpoint `/api/v2/addresses/{address}/logs` to get Logs Emitted by Address."),
             (
                 f"Use `direct_api_call` with endpoint `/api/v2/addresses/{address}/coin-balance-history-by-day` "
                 "to get daily native coin balance history."
             ),
-            (
-                f"Use `direct_api_call` with endpoint `/addresses/{address}/coin-balance-history` "
-                "to get native coin balance history."
-            ),
+            # Some instances expose this under /api/v2, others without the prefix
+            (
+                f"Use `direct_api_call` with endpoint `/api/v2/addresses/{address}/coin-balance-history` "
+                "to get native coin balance history."
+            ),

Or:

# Alternative: assert either form is present
alts = [
    f"Use `direct_api_call` with endpoint `/api/v2/addresses/{address}/coin-balance-history` to get native coin balance history.",
    f"Use `direct_api_call` with endpoint `/addresses/{address}/coin-balance-history` to get native coin balance history.",
]
assert any(a in result.instructions for a in alts)
blockscout_mcp_server/constants.py (1)

164-166: Placeholder name nit: transactions_hash → transaction_hash.

For consistency with common naming elsewhere.

-                    "path": "/api/v2/arbitrum/messages/withdrawals/{transactions_hash}",
-                    "description": "Get L2 to L1 messages for a specific transaction hash on Arbitrum.",
+                    "path": "/api/v2/arbitrum/messages/withdrawals/{transaction_hash}",
+                    "description": "Get L2 to L1 messages for a specific transaction hash on Arbitrum.",
tests/integration/test_direct_api_tools_integration.py (1)

29-35: Harden integration tests against transient network/pagination variance.

Add simple retries for httpx.RequestError and skip if no pagination after a few attempts to reduce flakiness.

@@
 @pytest.mark.integration
 @pytest.mark.asyncio
 async def test_direct_api_call_blocks_validated_pagination(mock_ctx):
     path = "/api/v2/addresses/0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97/blocks-validated"
-    first = await direct_api_call(chain_id="1", endpoint_path=path, ctx=mock_ctx)
+    # Basic retry on transport errors
+    attempts = 0
+    while True:
+        attempts += 1
+        try:
+            first = await direct_api_call(chain_id="1", endpoint_path=path, ctx=mock_ctx)
+            break
+        except Exception as e:
+            if attempts >= 3:
+                pytest.skip(f"Network instability or upstream issue: {e}")
+            continue
     assert first.pagination is not None
     next_params = first.pagination.next_call.params
     second = await direct_api_call(ctx=mock_ctx, **next_params)
-    assert isinstance(second.data, dict)
+    assert isinstance(second.data, dict)
+    # Optional: sanity-check the second page differs from the first when feasible
+    if isinstance(first.data, dict) and isinstance(second.data, dict):
+        if "items" in first.data and "items" in second.data:
+            assert first.data["items"] != second.data["items"], "Second page should differ from first when paginated"
SPEC.md (1)

585-611: Document explicit pagination support for direct_api_call.

To match our tool-doc guidance, add an explicit note that the tool supports pagination via the pagination.next_call cursor.

-### Direct API Call Tool (`direct_api_call`)
+### Direct API Call Tool (`direct_api_call`)
+
+SUPPORTS PAGINATION: If the response includes a `pagination` field, use the provided `next_call` (with `cursor`) to fetch additional pages.
blockscout_mcp_server/server.py (2)

51-62: Format function couples to dict shape; consider typed models or shared renderer

format_endpoint_groups assumes dicts with "group"/"chains_family". Prefer operating on DirectApiCommonGroup/DirectApiSpecificGroup or centralizing this rendering to avoid drift with constants/models.

Example refactor:

-def format_endpoint_groups(groups):
+def format_endpoint_groups(groups) -> str:
     formatted = []
-    for group in groups:
-        if "group" in group:
-            formatted.append(f'<group name="{group["group"]}">')
-            formatted.extend(f'"{endpoint["path"]}" - "{endpoint["description"]}"' for endpoint in group["endpoints"])
+    for group in groups:
+        if hasattr(group, "group"):
+            formatted.append(f'<group name="{getattr(group, "group")}">')
+            endpoints = getattr(group, "endpoints")
+            formatted.extend(f'"{e.path}" - "{e.description}"' for e in endpoints)
         elif "chains_family" in group:
             formatted.append(f'<chains_family name="{group["chains_family"]}">')
             formatted.extend(f'"{endpoint["path"]}" - "{endpoint["description"]}"' for endpoint in group["endpoints"])
             formatted.append("</chains_family>")
     return "\n".join(formatted)

Or render from the already-typed structure produced by unlock_blockchain_analysis to keep one source of truth.


65-66: Avoid recomputing lists from raw constants

common_endpoints/specific_endpoints are derived from constants again here, while initialization_tools builds the typed list for instructions. Consider importing the typed structure or moving the rendering to a shared util to prevent duplication.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
API.md (2)

499-505: Clarify query_params encoding and scope.

  • Consider a short note that query parameters are passed via bracket syntax (e.g., query_params[page]=1) and that only curated endpoints are supported by the server (to prevent misuse of arbitrary paths).

Apply this minimal tweak:

-| `endpoint_path` | `string` | Yes | The Blockscout API path to call (e.g., `/api/v2/stats`). |
-| `query_params` | `object` | No | Additional query parameters forwarded to the Blockscout API. |
+| `endpoint_path` | `string` | Yes | The Blockscout API path to call from the curated allowlist (e.g., `/api/v2/stats`). |
+| `query_params` | `object` | No | Additional query parameters forwarded to the Blockscout API. Use bracket syntax in the query string, e.g., `query_params[page]=1`. |

506-510: Optional: silence MD036 or align headings repo-wide.

markdownlint flags “Parameters” and “Example Request” as emphasis-as-heading. To stay consistent with the rest of this file, either:

  • Keep current style and add a top-of-file markdownlint disable for MD036, or
  • Convert all sections in API.md to real headings in a separate cleanup PR.

No change required here if you prefer consistency.

tests/api/test_routes.py (2)

558-572: Align test with PR goal: avoid unsupported limit; use page instead.

The PR removes limit usage in pagination tests. Update this unit test to avoid signaling limit as supported and keep consistency with API.md.

Apply:

-    url = "/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/stats&query_params[limit]=1"
+    url = "/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/stats&query_params[page]=1"
@@
-        query_params={"limit": "1"},
+        query_params={"page": "1"},

558-572: Cover “required-only” happy path.

Per route-test guidelines, add a success test with only required params (no query_params, no cursor).

Add:

@pytest.mark.asyncio
@patch("blockscout_mcp_server.api.routes.direct_api_call", new_callable=AsyncMock)
async def test_direct_api_call_required_only(mock_tool, client: AsyncClient):
    mock_tool.return_value = ToolResponse(data={"ok": True})
    response = await client.get("/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/stats")
    assert response.status_code == 200
    assert response.json()["data"] == {"ok": True}
    mock_tool.assert_called_once_with(chain_id="1", endpoint_path="/api/v2/stats", ctx=ANY)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 60d6393 and a893467.

📒 Files selected for processing (15)
  • API.md (1 hunks)
  • TESTING.md (1 hunks)
  • blockscout_mcp_server/api/routes.py (3 hunks)
  • blockscout_mcp_server/constants.py (1 hunks)
  • blockscout_mcp_server/models.py (2 hunks)
  • blockscout_mcp_server/server.py (5 hunks)
  • blockscout_mcp_server/tools/address_tools.py (1 hunks)
  • blockscout_mcp_server/tools/direct_api_tools.py (1 hunks)
  • blockscout_mcp_server/tools/initialization_tools.py (4 hunks)
  • gpt/instructions.md (1 hunks)
  • gpt/openapi.yaml (1 hunks)
  • tests/api/test_routes.py (1 hunks)
  • tests/integration/test_direct_api_tools_integration.py (1 hunks)
  • tests/tools/test_address_tools.py (1 hunks)
  • tests/tools/test_direct_api_tools.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • TESTING.md
🚧 Files skipped from review as they are similar to previous changes (12)
  • blockscout_mcp_server/tools/address_tools.py
  • blockscout_mcp_server/models.py
  • blockscout_mcp_server/server.py
  • blockscout_mcp_server/constants.py
  • blockscout_mcp_server/tools/initialization_tools.py
  • blockscout_mcp_server/api/routes.py
  • gpt/openapi.yaml
  • tests/tools/test_direct_api_tools.py
  • tests/tools/test_address_tools.py
  • gpt/instructions.md
  • blockscout_mcp_server/tools/direct_api_tools.py
  • tests/integration/test_direct_api_tools_integration.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/000-role-and-task.mdc)

**/*.py: The MCP server must be implemented in Python, as you are a senior Python developer and the expertise is in Python.
The MCP server must wrap Blockscout APIs and expose blockchain data (balances, tokens, NFTs, contract metadata) via the Model Context Protocol (MCP).
The MCP server must communicate with AI agents/chat applications through stdin.

**/*.py: Regular Python modules should generally not exceed 500 lines of code (LOC). If a module approaches this limit, consider splitting it into multiple focused modules (e.g., address_tools.py and address_tools_advanced.py) to maintain readability and logical organization.
ALL import statements must be placed at the top of the Python module, immediately after the module docstring (if present) and before any other code. Never insert imports inline near where the functionality is used. Follow PEP 8 import order.
ALL linting and formatting issues must be resolved before committing or pushing code. Use the Ruff rules defined in 300-ruff-lint-and-format.mdc to identify and fix issues.

**/*.py: Always run ruff check . --fix and ruff format . on generated code before suggesting commits or opening a PR
Avoid using # noqa: E501 for ordinary code lines; split long lines instead. Only use # noqa: E501 for docstrings or string literals that must exceed 120 characters.
Use Ruff to enforce a 120-character line length, compatible with Black formatting

Files:

  • tests/api/test_routes.py
tests/api/test_routes.py

📄 CodeRabbit inference engine (.cursor/rules/200-development-testing-workflow.mdc)

tests/api/test_routes.py: Create or update the appropriate unit test file when adding new functionality or modifying existing code: REST API endpoints in tests/api/test_routes.py
When editing REST API tests, follow the guidelines in 230-api-route-tests.mdc

tests/api/test_routes.py: Create a FastMCP instance and register routes using register_api_routes before making requests in tests for the REST API
Use httpx.AsyncClient with ASGITransport to call the routes in REST API route tests
Patch the wrapped tool functions with unittest.mock.patch and AsyncMock to avoid real network calls in REST API route tests
Assert that each endpoint returns the expected HTTP status and JSON content in REST API route tests
Verify that the patched tool was invoked exactly once with the expected arguments in REST API route tests
Include error-case tests to confirm a 400 response is returned when required query parameters are missing in REST API route tests
Test the static endpoints (e.g. /health, /, /llms.txt) to ensure they return the correct status code and content type after register_api_routes is called, and confirm these routes are unavailable on a clean FastMCP instance before registration
For each tool-based endpoint, create three tests: (1) success path with only required parameters, (2) success path including optional parameters if any, (3) failure path when a required parameter is missing, expecting HTTP 400
Name the test functions after the endpoint without the _rest suffix (e.g., use test_get_chains_list_success instead of test_get_chains_list_rest_success)

Files:

  • tests/api/test_routes.py
API.md

📄 CodeRabbit inference engine (.cursor/rules/800-api-documentation-guidelines.mdc)

API.md: Whenever a new MCP tool is added or an existing one is modified, its corresponding REST API endpoint in API.md MUST be added or updated.
Each endpoint documentation MUST follow the exact Markdown structure specified for consistency, including the heading format, parameter table, and example request.
The heading should be the human-readable tool name, with the function name in backticks.
The parameter table must clearly distinguish between required and optional parameters.
The curl example should demonstrate a realistic use case, including optional parameters where applicable.

Update REST API documentation in API.md for each new or updated endpoint, following the API documentation guidelines.

Files:

  • API.md
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-29T04:02:27.836Z
Learning: Applies to blockscout_mcp_server/tools/*.py : For paginated tools, accept an optional cursor argument and use apply_cursor_to_params to handle incoming cursors.
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Patch the wrapped tool functions with `unittest.mock.patch` and `AsyncMock` to avoid real network calls in REST API route tests

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:13:24.829Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-22T00:13:24.829Z
Learning: Applies to tests/api/test_routes.py : When editing REST API tests, follow the guidelines in 230-api-route-tests.mdc

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:13:24.829Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-22T00:13:24.829Z
Learning: Applies to tests/api/test_routes.py : Create or update the appropriate unit test file when adding new functionality or modifying existing code: REST API endpoints in tests/api/test_routes.py

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : For each tool-based endpoint, create three tests: (1) success path with only required parameters, (2) success path including optional parameters if any, (3) failure path when a required parameter is missing, expecting HTTP `400`

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Include error-case tests to confirm a `400` response is returned when required query parameters are missing in REST API route tests

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Verify that the patched tool was invoked exactly once with the expected arguments in REST API route tests

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Test the static endpoints (e.g. `/health`, `/`, `/llms.txt`) to ensure they return the correct status code and content type after `register_api_routes` is called, and confirm these routes are unavailable on a clean `FastMCP` instance before registration

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Name the test functions after the endpoint without the `_rest` suffix (e.g., use `test_get_chains_list_success` instead of `test_get_chains_list_rest_success`)

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:13:40.792Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:13:40.792Z
Learning: Applies to tests/tools/* : Ensure all external API calls in tests are properly mocked using unittest.mock.patch and AsyncMock.

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Assert that each endpoint returns the expected HTTP status and JSON content in REST API route tests

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:13:40.792Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:13:40.792Z
Learning: Applies to tests/tools/* : Assert that mocked API helper functions (such as make_blockscout_request) are called exactly once with the correct api_path and params in tests.

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Use `httpx.AsyncClient` with `ASGITransport` to call the routes in REST API route tests

Applied to files:

  • tests/api/test_routes.py
🧬 Code graph analysis (1)
tests/api/test_routes.py (1)
blockscout_mcp_server/models.py (1)
  • ToolResponse (375-400)
🪛 markdownlint-cli2 (0.17.2)
API.md

497-497: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


506-506: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run Integration Tests
🔇 Additional comments (1)
API.md (1)

491-495: Docs addition looks solid and consistent with the rest of API.md.

Clear name, route, and short description. Nice.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
tests/api/test_routes.py (2)

573-588: Cursor + query_params coverage — LGTM (addresses prior suggestion)

Validates nested query param parsing and cursor forwarding; aligns with pagination contract and removes any reliance on the unsupported limit param.

Run to confirm no lingering uses of the unsupported "limit" parameter remain in tests:

#!/bin/bash
# Search repo for any residual 'limit' usage in tests (query string or nested query_params)
rg -nP -C2 '(?:^|[?&])limit=\d+|query_params\[(?:[^]]*limit[^]]*)\]' tests || true

597-602: Missing chain_id negative test — LGTM (addresses prior suggestion)

Good parallel coverage for the other required parameter.

🧹 Nitpick comments (1)
tests/api/test_routes.py (1)

590-595: Clarify test name for missing param

Minor: rename to be explicit about which param is missing.

-@pytest.mark.asyncio
-async def test_direct_api_call_missing_param(client: AsyncClient):
+@pytest.mark.asyncio
+async def test_direct_api_call_missing_endpoint_path(client: AsyncClient):
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a893467 and 751196c.

📒 Files selected for processing (1)
  • tests/api/test_routes.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/000-role-and-task.mdc)

**/*.py: The MCP server must be implemented in Python, as you are a senior Python developer and the expertise is in Python.
The MCP server must wrap Blockscout APIs and expose blockchain data (balances, tokens, NFTs, contract metadata) via the Model Context Protocol (MCP).
The MCP server must communicate with AI agents/chat applications through stdin.

**/*.py: Regular Python modules should generally not exceed 500 lines of code (LOC). If a module approaches this limit, consider splitting it into multiple focused modules (e.g., address_tools.py and address_tools_advanced.py) to maintain readability and logical organization.
ALL import statements must be placed at the top of the Python module, immediately after the module docstring (if present) and before any other code. Never insert imports inline near where the functionality is used. Follow PEP 8 import order.
ALL linting and formatting issues must be resolved before committing or pushing code. Use the Ruff rules defined in 300-ruff-lint-and-format.mdc to identify and fix issues.

**/*.py: Always run ruff check . --fix and ruff format . on generated code before suggesting commits or opening a PR
Avoid using # noqa: E501 for ordinary code lines; split long lines instead. Only use # noqa: E501 for docstrings or string literals that must exceed 120 characters.
Use Ruff to enforce a 120-character line length, compatible with Black formatting

Files:

  • tests/api/test_routes.py
tests/api/test_routes.py

📄 CodeRabbit inference engine (.cursor/rules/200-development-testing-workflow.mdc)

tests/api/test_routes.py: Create or update the appropriate unit test file when adding new functionality or modifying existing code: REST API endpoints in tests/api/test_routes.py
When editing REST API tests, follow the guidelines in 230-api-route-tests.mdc

tests/api/test_routes.py: Create a FastMCP instance and register routes using register_api_routes before making requests in tests for the REST API
Use httpx.AsyncClient with ASGITransport to call the routes in REST API route tests
Patch the wrapped tool functions with unittest.mock.patch and AsyncMock to avoid real network calls in REST API route tests
Assert that each endpoint returns the expected HTTP status and JSON content in REST API route tests
Verify that the patched tool was invoked exactly once with the expected arguments in REST API route tests
Include error-case tests to confirm a 400 response is returned when required query parameters are missing in REST API route tests
Test the static endpoints (e.g. /health, /, /llms.txt) to ensure they return the correct status code and content type after register_api_routes is called, and confirm these routes are unavailable on a clean FastMCP instance before registration
For each tool-based endpoint, create three tests: (1) success path with only required parameters, (2) success path including optional parameters if any, (3) failure path when a required parameter is missing, expecting HTTP 400
Name the test functions after the endpoint without the _rest suffix (e.g., use test_get_chains_list_success instead of test_get_chains_list_rest_success)

Files:

  • tests/api/test_routes.py
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-29T04:02:27.836Z
Learning: Applies to api/routes.py : Expose each new tool via the REST API by creating a wrapper endpoint in api/routes.py and registering the route under the /v1/ prefix.
📚 Learning: 2025-07-22T00:13:24.829Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-22T00:13:24.829Z
Learning: Applies to tests/api/test_routes.py : When editing REST API tests, follow the guidelines in 230-api-route-tests.mdc

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:13:24.829Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-22T00:13:24.829Z
Learning: Applies to tests/api/test_routes.py : Create or update the appropriate unit test file when adding new functionality or modifying existing code: REST API endpoints in tests/api/test_routes.py

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : For each tool-based endpoint, create three tests: (1) success path with only required parameters, (2) success path including optional parameters if any, (3) failure path when a required parameter is missing, expecting HTTP `400`

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Patch the wrapped tool functions with `unittest.mock.patch` and `AsyncMock` to avoid real network calls in REST API route tests

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Include error-case tests to confirm a `400` response is returned when required query parameters are missing in REST API route tests

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:13:40.792Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:13:40.792Z
Learning: Applies to tests/tools/* : Ensure all external API calls in tests are properly mocked using unittest.mock.patch and AsyncMock.

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-29T04:02:27.836Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-29T04:02:27.836Z
Learning: Applies to blockscout_mcp_server/tools/*.py : For paginated tools, accept an optional cursor argument and use apply_cursor_to_params to handle incoming cursors.

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:04.976Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/220-integration-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:14:04.976Z
Learning: Applies to tests/integration/test_*_integration.py : Integration tests for tools supporting cursor-based pagination must perform a two-step test: first call without a cursor, extract the cursor, then call again with the cursor and assert the second page's data is different from the first.

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Verify that the patched tool was invoked exactly once with the expected arguments in REST API route tests

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:13:40.792Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:13:40.792Z
Learning: Applies to tests/tools/* : Assert that mocked API helper functions (such as make_blockscout_request) are called exactly once with the correct api_path and params in tests.

Applied to files:

  • tests/api/test_routes.py
🧬 Code graph analysis (1)
tests/api/test_routes.py (1)
blockscout_mcp_server/models.py (1)
  • ToolResponse (375-400)
🔇 Additional comments (1)
tests/api/test_routes.py (1)

558-571: Direct API happy-path test — LGTM

Covers required params only and asserts correct forwarding to the tool. No issues.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
API.md (2)

491-495: Document cursor-only pagination; call out that limit/page_size are ignored.

This endpoint’s behavior (and this PR’s intent) will be clearer if we explicitly say that page sizing is server-defined and any limit-like params are ignored.

-Allows calling a curated raw Blockscout API endpoint for advanced or chain-specific data.
+Allows calling a curated raw Blockscout API endpoint for advanced or chain-specific data.
+
+Note: Pagination is cursor-based. The server applies a chain-specific default page size; limit or page_size
+parameters are ignored. Use the `cursor` from the previous response's `pagination.next_call.params` to
+fetch subsequent pages.

497-506: Fix markdownlint MD036 by using subheadings instead of bold.

Switch “Parameters” and “Example Request” to proper headings to satisfy the linter without altering content.

-**Parameters**
+#### Parameters
@@
-**Example Request**
+#### Example Request
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 751196c and 2cf0d89.

📒 Files selected for processing (2)
  • API.md (1 hunks)
  • tests/api/test_routes.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/000-role-and-task.mdc)

**/*.py: The MCP server must be implemented in Python, as you are a senior Python developer and the expertise is in Python.
The MCP server must wrap Blockscout APIs and expose blockchain data (balances, tokens, NFTs, contract metadata) via the Model Context Protocol (MCP).
The MCP server must communicate with AI agents/chat applications through stdin.

**/*.py: Regular Python modules should generally not exceed 500 lines of code (LOC). If a module approaches this limit, consider splitting it into multiple focused modules (e.g., address_tools.py and address_tools_advanced.py) to maintain readability and logical organization.
ALL import statements must be placed at the top of the Python module, immediately after the module docstring (if present) and before any other code. Never insert imports inline near where the functionality is used. Follow PEP 8 import order.
ALL linting and formatting issues must be resolved before committing or pushing code. Use the Ruff rules defined in 300-ruff-lint-and-format.mdc to identify and fix issues.

**/*.py: Always run ruff check . --fix and ruff format . on generated code before suggesting commits or opening a PR
Avoid using # noqa: E501 for ordinary code lines; split long lines instead. Only use # noqa: E501 for docstrings or string literals that must exceed 120 characters.
Use Ruff to enforce a 120-character line length, compatible with Black formatting

Files:

  • tests/api/test_routes.py
tests/api/test_routes.py

📄 CodeRabbit inference engine (.cursor/rules/200-development-testing-workflow.mdc)

tests/api/test_routes.py: Create or update the appropriate unit test file when adding new functionality or modifying existing code: REST API endpoints in tests/api/test_routes.py
When editing REST API tests, follow the guidelines in 230-api-route-tests.mdc

tests/api/test_routes.py: Create a FastMCP instance and register routes using register_api_routes before making requests in tests for the REST API
Use httpx.AsyncClient with ASGITransport to call the routes in REST API route tests
Patch the wrapped tool functions with unittest.mock.patch and AsyncMock to avoid real network calls in REST API route tests
Assert that each endpoint returns the expected HTTP status and JSON content in REST API route tests
Verify that the patched tool was invoked exactly once with the expected arguments in REST API route tests
Include error-case tests to confirm a 400 response is returned when required query parameters are missing in REST API route tests
Test the static endpoints (e.g. /health, /, /llms.txt) to ensure they return the correct status code and content type after register_api_routes is called, and confirm these routes are unavailable on a clean FastMCP instance before registration
For each tool-based endpoint, create three tests: (1) success path with only required parameters, (2) success path including optional parameters if any, (3) failure path when a required parameter is missing, expecting HTTP 400
Name the test functions after the endpoint without the _rest suffix (e.g., use test_get_chains_list_success instead of test_get_chains_list_rest_success)

Files:

  • tests/api/test_routes.py
API.md

📄 CodeRabbit inference engine (.cursor/rules/800-api-documentation-guidelines.mdc)

API.md: Whenever a new MCP tool is added or an existing one is modified, its corresponding REST API endpoint in API.md MUST be added or updated.
Each endpoint documentation MUST follow the exact Markdown structure specified for consistency, including the heading format, parameter table, and example request.
The heading should be the human-readable tool name, with the function name in backticks.
The parameter table must clearly distinguish between required and optional parameters.
The curl example should demonstrate a realistic use case, including optional parameters where applicable.

Update REST API documentation in API.md for each new or updated endpoint, following the API documentation guidelines.

Files:

  • API.md
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-29T04:02:27.836Z
Learning: Applies to api/routes.py : Expose each new tool via the REST API by creating a wrapper endpoint in api/routes.py and registering the route under the /v1/ prefix.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-22T00:13:24.829Z
Learning: Applies to tests/api/test_routes.py : Create or update the appropriate unit test file when adding new functionality or modifying existing code: REST API endpoints in tests/api/test_routes.py
📚 Learning: 2025-07-22T00:13:24.829Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-22T00:13:24.829Z
Learning: Applies to tests/api/test_routes.py : When editing REST API tests, follow the guidelines in 230-api-route-tests.mdc

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:13:24.829Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-22T00:13:24.829Z
Learning: Applies to tests/api/test_routes.py : Create or update the appropriate unit test file when adding new functionality or modifying existing code: REST API endpoints in tests/api/test_routes.py

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-29T04:02:27.836Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-29T04:02:27.836Z
Learning: Applies to blockscout_mcp_server/tools/*.py : For paginated tools, accept an optional cursor argument and use apply_cursor_to_params to handle incoming cursors.

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:04.976Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/220-integration-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:14:04.976Z
Learning: Applies to tests/integration/test_*_integration.py : Integration tests for tools supporting cursor-based pagination must perform a two-step test: first call without a cursor, extract the cursor, then call again with the cursor and assert the second page's data is different from the first.

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Include error-case tests to confirm a `400` response is returned when required query parameters are missing in REST API route tests

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : For each tool-based endpoint, create three tests: (1) success path with only required parameters, (2) success path including optional parameters if any, (3) failure path when a required parameter is missing, expecting HTTP `400`

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Patch the wrapped tool functions with `unittest.mock.patch` and `AsyncMock` to avoid real network calls in REST API route tests

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:13:40.792Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:13:40.792Z
Learning: Applies to tests/tools/* : Ensure all external API calls in tests are properly mocked using unittest.mock.patch and AsyncMock.

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Verify that the patched tool was invoked exactly once with the expected arguments in REST API route tests

Applied to files:

  • tests/api/test_routes.py
📚 Learning: 2025-07-22T00:13:40.792Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:13:40.792Z
Learning: Applies to tests/tools/* : Assert that mocked API helper functions (such as make_blockscout_request) are called exactly once with the correct api_path and params in tests.

Applied to files:

  • tests/api/test_routes.py
🧬 Code graph analysis (1)
tests/api/test_routes.py (1)
blockscout_mcp_server/models.py (1)
  • ToolResponse (375-400)
🪛 markdownlint-cli2 (0.17.2)
API.md

497-497: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


506-506: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (1)
tests/api/test_routes.py (1)

558-597: LGTM: Route wrapper tests cover required, optional (query_params + cursor), and 400 cases.

  • Correct patch target (blockscout_mcp_server.api.routes.direct_api_call)
  • Verifies forwarded args (including dict decoding for query_params) and single invocation

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (8)
API.md (2)

497-497: Use headings instead of bold for “Parameters” and “Example Request” (fix MD036).

Swap bold text for H4 headings to satisfy markdownlint and keep structure consistent.

-**Parameters**
+#### Parameters
@@
-**Example Request**
+#### Example Request

Also applies to: 506-506


491-510: Minor doc polish: reinforce “no query in endpoint_path”.

Add a short note right under the section header to reiterate that endpoint_path must not include a query string (aligns with tool validation).

 ### Direct API Call (`direct_api_call`)
 
-Allows calling a curated raw Blockscout API endpoint for advanced or chain-specific data.
+Allows calling a curated raw Blockscout API endpoint for advanced or chain-specific data.
+Note: Do not include query strings in `endpoint_path`; pass all parameters via `query_params[...]`.
TESTING.md (1)

230-235: Add a second-step pagination example.

Show a follow-up curl using the returned cursor to demonstrate the paging workflow end-to-end.

AGENTS.md (1)

286-288: Parameter naming inconsistency with API: use transaction_hash (not hash).

Aligns with API.md and tool signature used across docs.

- * `transaction_tools.py`: Implements `get_transactions_by_address(chain_id, address, age_from, age_to, methods, cursor=None)`, `get_token_transfers_by_address(chain_id, address, age_from, age_to, token, cursor=None)`, `get_transaction_info(chain_id, hash, include_raw_input=False)`, `transaction_summary(chain_id, hash)`, `get_transaction_logs(chain_id, hash, cursor=None)`, etc.
+ * `transaction_tools.py`: Implements `get_transactions_by_address(chain_id, address, age_from, age_to, methods, cursor=None)`, `get_token_transfers_by_address(chain_id, address, age_from, age_to, token, cursor=None)`, `get_transaction_info(chain_id, transaction_hash, include_raw_input=False)`, `transaction_summary(chain_id, transaction_hash)`, `get_transaction_logs(chain_id, transaction_hash, cursor=None)`, etc.
tests/tools/test_direct_api_tools.py (3)

37-37: Prefer await_count for AsyncMock assertions.

Assert awaited calls, not just invocations, for tighter guarantees and consistency with repo tests.

-        assert mock_ctx.report_progress.call_count == 3
+        assert mock_ctx.report_progress.await_count == 3
@@
-        assert mock_ctx.report_progress.call_count == 3
+        assert mock_ctx.report_progress.await_count == 3
@@
-        assert mock_ctx.report_progress.call_count == 3
+        assert mock_ctx.report_progress.await_count == 3
@@
-        assert mock_ctx.report_progress.call_count == 2
+        assert mock_ctx.report_progress.await_count == 2
@@
-        assert mock_ctx.report_progress.call_count == 1
+        assert mock_ctx.report_progress.await_count == 1

Also applies to: 91-91, 126-126, 150-150, 173-173


154-167: Unnecessary patch of make_blockscout_request in the negative-path test.

The function is intentionally not called; removing this patch reduces noise.

-    with (
-        patch(
-            "blockscout_mcp_server.tools.direct_api_tools.get_blockscout_base_url",
-            new_callable=AsyncMock,
-        ) as mock_get_url,
-        patch(
-            "blockscout_mcp_server.tools.direct_api_tools.make_blockscout_request",
-            new_callable=AsyncMock,
-        ) as mock_request,
-    ):
+    with patch(
+        "blockscout_mcp_server.tools.direct_api_tools.get_blockscout_base_url",
+        new_callable=AsyncMock,
+    ) as mock_get_url:
@@
-        mock_request.assert_not_called()

95-126: Optional: add a test asserting query_params propagation into next_call when provided.

Covers the branch where initial call includes query_params and ensures they’re retained in pagination.

I can draft this additional test if helpful.

blockscout_mcp_server/tools/transaction_tools.py (1)

548-555: Make the instruction copy-pasteable (show full call signature)

Small clarity win: present a direct function-call example including chain_id so users can paste/run without editing sentence fragments.

Apply this diff:

-    instructions = [
-        (
-            "To check for ERC-4337 User Operations related to this tx, call "
-            f"`direct_api_call` with endpoint `/api/v2/proxy/account-abstraction/operations` "
-            f"with query_params={{'transaction_hash': '{transaction_hash}'}}."
-        )
-    ]
+    instructions = [
+        (
+            "Check ERC-4337 User Operations for this tx with direct_api_call: "
+            "direct_api_call(chain_id, '/api/v2/proxy/account-abstraction/operations', "
+            f"{{'transaction_hash': '{transaction_hash}'}})"
+        )
+    ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2cf0d89 and 90972da.

📒 Files selected for processing (9)
  • AGENTS.md (3 hunks)
  • API.md (1 hunks)
  • TESTING.md (1 hunks)
  • blockscout_mcp_server/tools/direct_api_tools.py (1 hunks)
  • blockscout_mcp_server/tools/transaction_tools.py (1 hunks)
  • gpt/openapi.yaml (1 hunks)
  • tests/integration/test_direct_api_tools_integration.py (1 hunks)
  • tests/tools/test_direct_api_tools.py (1 hunks)
  • tests/tools/test_transaction_tools_2.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/integration/test_direct_api_tools_integration.py
  • tests/tools/test_transaction_tools_2.py
  • blockscout_mcp_server/tools/direct_api_tools.py
  • gpt/openapi.yaml
🧰 Additional context used
📓 Path-based instructions (6)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/000-role-and-task.mdc)

**/*.py: The MCP server must be implemented in Python, as you are a senior Python developer and the expertise is in Python.
The MCP server must wrap Blockscout APIs and expose blockchain data (balances, tokens, NFTs, contract metadata) via the Model Context Protocol (MCP).
The MCP server must communicate with AI agents/chat applications through stdin.

**/*.py: Regular Python modules should generally not exceed 500 lines of code (LOC). If a module approaches this limit, consider splitting it into multiple focused modules (e.g., address_tools.py and address_tools_advanced.py) to maintain readability and logical organization.
ALL import statements must be placed at the top of the Python module, immediately after the module docstring (if present) and before any other code. Never insert imports inline near where the functionality is used. Follow PEP 8 import order.
ALL linting and formatting issues must be resolved before committing or pushing code. Use the Ruff rules defined in 300-ruff-lint-and-format.mdc to identify and fix issues.

**/*.py: Always run ruff check . --fix and ruff format . on generated code before suggesting commits or opening a PR
Avoid using # noqa: E501 for ordinary code lines; split long lines instead. Only use # noqa: E501 for docstrings or string literals that must exceed 120 characters.
Use Ruff to enforce a 120-character line length, compatible with Black formatting

Files:

  • tests/tools/test_direct_api_tools.py
  • blockscout_mcp_server/tools/transaction_tools.py
tests/tools/test_*.py

📄 CodeRabbit inference engine (.cursor/rules/200-development-testing-workflow.mdc)

Create or update the appropriate unit test file when adding new functionality or modifying existing code: Tool functions in tests/tools/test_{tool_module}.py

Files:

  • tests/tools/test_direct_api_tools.py
tests/tools/*

📄 CodeRabbit inference engine (.cursor/rules/210-unit-testing-guidelines.mdc)

tests/tools/*: Each unit test in tests/tools/* must be narrow and specific; a single test should verify one specific behavior or scenario. If a test covers multiple scenarios or input parameter groups, split it into separate tests.
Use the mock_ctx pytest fixture from tests/conftest.py for mocking the MCP Context object in tests; do not create manual MagicMock instances for the context within test functions.
When testing tools that return a ToolResponse object, do not parse JSON from string results in your test. Instead, mock the serialization function (json.dumps) if used internally, and make assertions on the structured ToolResponse object and its attributes.
When testing tools that transform a list of items, programmatically generate the expected_result from the mock_api_response to keep tests maintainable, while still documenting the transformation logic.
Always verify the number of calls to mock_ctx.report_progress in tests to ensure progress tracking is tested.
Assert that mocked API helper functions (such as make_blockscout_request) are called exactly once with the correct api_path and params in tests.
For tools using make_request_with_periodic_progress, mock the wrapper itself and assert that it was called with the correct arguments (request_function, request_args, etc.).
Unit test files in tests/tools/* must not exceed 500 lines of code (LOC). If a file approaches this limit, split tests into multiple files to maintain readability and focus.
Write tests covering success scenarios, error cases, and edge cases in unit test files.
Ensure all external API calls in tests are properly mocked using unittest.mock.patch and AsyncMock.
Group related tests using descriptive class names or clear function naming patterns.

Files:

  • tests/tools/test_direct_api_tools.py
AGENTS.md

📄 CodeRabbit inference engine (.cursor/rules/110-new-mcp-tool.mdc)

Update AGENTS.md to document new or modified tool modules, including updates to the directory tree and examples sections.

Files:

  • AGENTS.md
blockscout_mcp_server/tools/*.py

📄 CodeRabbit inference engine (.cursor/rules/110-new-mcp-tool.mdc)

blockscout_mcp_server/tools/*.py: Create or modify a tool module file in blockscout_mcp_server/tools/ for each new tool, using @log_tool_invocation to decorate each tool function.
All tools MUST return a strongly-typed ToolResponse[YourDataModel] instead of generic ToolResponse[dict].
For tools that query Blockscout API, use get_blockscout_base_url for dynamic chain resolution and make_blockscout_request for API calls.
For tools that use fixed API endpoints (like BENS), use the appropriate request helper (e.g., make_bens_request) from tools/common.py.
All tools MUST return a standardized ToolResponse[YourDataModel] object using the build_tool_response helper.
For paginated tools, accept an optional cursor argument and use apply_cursor_to_params to handle incoming cursors.
For paginated tools, use create_items_pagination from tools/common.py to handle slicing and pagination in responses.
For paginated tools, include the exact notice 'SUPPORTS PAGINATION: If response includes 'pagination' field, use the provided next_call to get additional pages.' in the tool docstring.
When returning addresses from Blockscout API responses, simplify address objects to a single address string in the tool output.
Truncate large data fields (such as raw 'data' or deeply nested values) in tool responses to save LLM context, and add notes about truncation.
Recursively truncate long strings in nested data structures in tool responses, replacing them with a structured object to signal truncation and adding notes.
Always raise exceptions for error conditions (e.g., ValueError, RuntimeError, TimeoutError) instead of returning ToolResponse objects with error messages in notes.
Use the report_and_log_progress helper from tools/common.py for all progress reporting in tool functions, instead of calling ctx.report_progress directly.
When making multiple independent API calls in a tool, use asyncio.gather with return_exceptions=True for concurrent execution and proper error handling.

Files:

  • blockscout_mcp_server/tools/transaction_tools.py
API.md

📄 CodeRabbit inference engine (.cursor/rules/800-api-documentation-guidelines.mdc)

API.md: Whenever a new MCP tool is added or an existing one is modified, its corresponding REST API endpoint in API.md MUST be added or updated.
Each endpoint documentation MUST follow the exact Markdown structure specified for consistency, including the heading format, parameter table, and example request.
The heading should be the human-readable tool name, with the function name in backticks.
The parameter table must clearly distinguish between required and optional parameters.
The curl example should demonstrate a realistic use case, including optional parameters where applicable.

Update REST API documentation in API.md for each new or updated endpoint, following the API documentation guidelines.

Files:

  • API.md
🧠 Learnings (17)
📓 Common learnings
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-29T04:02:27.836Z
Learning: Applies to api/routes.py : Expose each new tool via the REST API by creating a wrapper endpoint in api/routes.py and registering the route under the /v1/ prefix.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-22T00:13:24.829Z
Learning: Applies to tests/api/test_routes.py : Create or update the appropriate unit test file when adding new functionality or modifying existing code: REST API endpoints in tests/api/test_routes.py
📚 Learning: 2025-07-22T00:13:24.829Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-22T00:13:24.829Z
Learning: Applies to tests/integration/test_*_integration.py : Add integration tests for tool functions in tests/integration/test_{tool_module}_integration.py when adding new tools that interact with live APIs

Applied to files:

  • tests/tools/test_direct_api_tools.py
  • AGENTS.md
📚 Learning: 2025-07-22T00:13:40.792Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:13:40.792Z
Learning: Applies to tests/tools/* : Ensure all external API calls in tests are properly mocked using unittest.mock.patch and AsyncMock.

Applied to files:

  • tests/tools/test_direct_api_tools.py
📚 Learning: 2025-07-22T00:13:40.792Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:13:40.792Z
Learning: Applies to tests/tools/* : Always verify the number of calls to mock_ctx.report_progress in tests to ensure progress tracking is tested.

Applied to files:

  • tests/tools/test_direct_api_tools.py
📚 Learning: 2025-07-22T00:13:40.792Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:13:40.792Z
Learning: Applies to tests/tools/* : For tools using make_request_with_periodic_progress, mock the wrapper itself and assert that it was called with the correct arguments (request_function, request_args, etc.).

Applied to files:

  • tests/tools/test_direct_api_tools.py
📚 Learning: 2025-07-29T04:02:27.836Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-29T04:02:27.836Z
Learning: Applies to blockscout_mcp_server/tools/*.py : For paginated tools, accept an optional cursor argument and use apply_cursor_to_params to handle incoming cursors.

Applied to files:

  • tests/tools/test_direct_api_tools.py
📚 Learning: 2025-07-22T00:14:04.976Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/220-integration-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:14:04.976Z
Learning: Applies to tests/integration/test_*_integration.py : Integration tests for tools supporting cursor-based pagination must perform a two-step test: first call without a cursor, extract the cursor, then call again with the cursor and assert the second page's data is different from the first.

Applied to files:

  • tests/tools/test_direct_api_tools.py
📚 Learning: 2025-07-22T00:14:13.016Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/230-api-route-tests.mdc:0-0
Timestamp: 2025-07-22T00:14:13.016Z
Learning: Applies to tests/api/test_routes.py : Patch the wrapped tool functions with `unittest.mock.patch` and `AsyncMock` to avoid real network calls in REST API route tests

Applied to files:

  • tests/tools/test_direct_api_tools.py
📚 Learning: 2025-07-22T00:13:40.792Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:13:40.792Z
Learning: Applies to tests/tools/* : Assert that mocked API helper functions (such as make_blockscout_request) are called exactly once with the correct api_path and params in tests.

Applied to files:

  • tests/tools/test_direct_api_tools.py
📚 Learning: 2025-07-29T04:02:27.836Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-29T04:02:27.836Z
Learning: Applies to AGENTS.md : Update AGENTS.md to document new or modified tool modules, including updates to the directory tree and examples sections.

Applied to files:

  • AGENTS.md
📚 Learning: 2025-07-22T00:14:04.976Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/220-integration-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:14:04.976Z
Learning: Applies to tests/integration/test_*_integration.py : Tool-level integration tests targeting high-level MCP tool functions (e.g., get_latest_block, get_tokens_by_address) must be located in files matching tests/integration/test_*_integration.py and validate data extraction and schema against live API responses.

Applied to files:

  • AGENTS.md
📚 Learning: 2025-07-22T00:11:07.554Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/000-role-and-task.mdc:0-0
Timestamp: 2025-07-22T00:11:07.554Z
Learning: Applies to **/*.py : The MCP server must wrap Blockscout APIs and expose blockchain data (balances, tokens, NFTs, contract metadata) via the Model Context Protocol (MCP).

Applied to files:

  • AGENTS.md
📚 Learning: 2025-07-22T00:14:04.976Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/220-integration-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:14:04.976Z
Learning: Applies to tests/integration/*.py : Separate helper-level integration tests from tool-level integration tests into different files when appropriate.

Applied to files:

  • AGENTS.md
📚 Learning: 2025-07-22T00:14:04.976Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/220-integration-testing-guidelines.mdc:0-0
Timestamp: 2025-07-22T00:14:04.976Z
Learning: Applies to tests/integration/test_common_helpers.py : Helper-level integration tests targeting low-level helper functions in tools/common.py (e.g., make_blockscout_request, get_blockscout_base_url) must be located in tests/integration/test_common_helpers.py and verify basic network connectivity and response structure.

Applied to files:

  • AGENTS.md
📚 Learning: 2025-07-22T00:13:24.829Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-22T00:13:24.829Z
Learning: Applies to tests/integration/test_common_helpers.py : Add integration tests for helper functions in tests/integration/test_common_helpers.py when modifying helper functions in tools/common.py

Applied to files:

  • AGENTS.md
📚 Learning: 2025-07-10T23:38:26.789Z
Learnt from: akolotov
PR: blockscout/mcp-server#145
File: API.md:170-176
Timestamp: 2025-07-10T23:38:26.789Z
Learning: When reviewing API parameter names, always verify the actual function signature in the source code rather than making assumptions. The get_transaction_info function in blockscout_mcp_server/tools/transaction_tools.py uses 'transaction_hash' as the parameter name, not 'hash'.

Applied to files:

  • blockscout_mcp_server/tools/transaction_tools.py
📚 Learning: 2025-07-29T04:02:27.836Z
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-29T04:02:27.836Z
Learning: Applies to API.md : Update REST API documentation in API.md for each new or updated endpoint, following the API documentation guidelines.

Applied to files:

  • TESTING.md
🧬 Code graph analysis (2)
tests/tools/test_direct_api_tools.py (3)
blockscout_mcp_server/models.py (2)
  • DirectApiData (62-65)
  • ToolResponse (375-400)
blockscout_mcp_server/tools/direct_api_tools.py (1)
  • direct_api_call (19-87)
tests/conftest.py (1)
  • mock_ctx (8-13)
blockscout_mcp_server/tools/transaction_tools.py (1)
blockscout_mcp_server/tools/common.py (1)
  • build_tool_response (489-530)
🪛 markdownlint-cli2 (0.17.2)
API.md

497-497: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


506-506: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Docker build and docker push
  • GitHub Check: Run Integration Tests
🔇 Additional comments (3)
AGENTS.md (1)

38-39: Good addition of direct_api_tools in structure.

Accurately documents the new module and placement. No issues.

blockscout_mcp_server/tools/transaction_tools.py (2)

548-555: Actionable follow-up via direct_api_call looks good

Adding contextual instructions that point to the AA operations endpoint is helpful and aligns with the acceptance criteria around suggesting follow-up endpoints.


548-555: Confirm AA endpoint filter parameter

The current implementation instructs users to call
/api/v2/proxy/account-abstraction/operations
with query_params={'transaction_hash': '<tx hash>'}, but:
• API.md and TESTING.md examples only show filtering by sender via query_params[sender], not by transaction hash.
• The documented path for fetching a specific User Operation is
/api/v2/proxy/account-abstraction/operations/{user_operation_hash},
suggesting the endpoint may not support a transaction_hash query parameter.

Please verify against your target Blockscout instances:

  • Does /api/v2/proxy/account-abstraction/operations accept a transaction_hash query parameter (versus tx_hash, hash, or another name)?
  • If not, update the instructions to use the path parameter (e.g. /operations/{user_operation_hash}) or the correct query key.
  • Consider adding a test or example in TESTING.md to illustrate the supported filtering method.

@akolotov akolotov requested a review from Copilot August 27, 2025 15:56
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a new direct_api_call MCP tool that provides access to curated Blockscout API endpoints for specialized or chain-specific data not covered by existing tools. The implementation includes comprehensive testing, API documentation, and integration with the server's instructions system.

  • Introduces direct_api_call tool with support for pagination and query parameters
  • Adds comprehensive unit and integration tests covering various usage scenarios
  • Updates existing tools to provide contextual suggestions for using the direct API feature

Reviewed Changes

Copilot reviewed 29 out of 29 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
blockscout_mcp_server/tools/direct_api_tools.py New tool implementation with pagination support and parameter validation
blockscout_mcp_server/models.py Added data models for direct API responses and endpoint curation
blockscout_mcp_server/constants.py Added curated endpoint list and usage rules
tests/tools/test_direct_api_tools.py Comprehensive unit tests for the new tool
tests/integration/test_direct_api_tools_integration.py Integration tests including pagination scenarios
Various configuration files Version bumps and documentation updates

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
API.md (4)

497-505: Fix markdownlint MD036: use headings instead of bold for “Parameters”.

Convert the bold line to a subheading for consistency with linting.

-**Parameters**
+#### Parameters
@@
-| `query_params` | `object` | No | Additional query parameters forwarded to the Blockscout API. Use bracket syntax in the query string, e.g., `query_params[page]=1`. |
+| `query_params` | `object` | No | Extra query parameters to forward upstream. Any query string key other than `chain_id`, `endpoint_path`, and `cursor` is forwarded automatically. Bracket syntax (e.g., `query_params[page]=1`) is also supported. |

506-510: Fix markdownlint MD036: use heading for “Example Request” and show both param styles.

Also add a second example including cursor.

-**Example Request**
+#### Example Request
@@
-```bash
-curl "http://127.0.0.1:8000/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/proxy/account-abstraction/operations&query_params[sender]=0x91f51371D33e4E50e838057E8045265372f8d448"
-```
+```bash
+# Style 1: direct query keys (preferred)
+curl "http://127.0.0.1:8000/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/proxy/account-abstraction/operations&sender=0x91f51371D33e4E50e838057E8045265372f8d448"
+
+# Style 2: bracket syntax (also supported)
+curl "http://127.0.0.1:8000/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/proxy/account-abstraction/operations&query_params[sender]=0x91f51371D33e4E50e838057E8045265372f8d448"
+
+# Paginating with a cursor
+curl "http://127.0.0.1:8000/v1/direct_api_call?chain_id=1&endpoint_path=/api/v2/proxy/account-abstraction/operations&cursor=eyJwYWdlIjoyfQ=="
+```

495-505: Add explicit pagination note for direct_api_call.

State that next_call.tool_name will be direct_api_call and the cursor is opaque.

 `GET /v1/direct_api_call`
 
-#### Parameters
+> Pagination: When the response includes `pagination.next_call`, the `tool_name` will be `direct_api_call`. Pass the returned opaque `cursor` back to this endpoint unchanged.
+
+#### Parameters

503-505: Document unsupported limit behavior to prevent test/user confusion.

Some Blockscout endpoints ignore or reject limit. Encourage cursor-based pagination and server defaults.

 | `cursor` | `string` | No | The cursor for pagination from a previous response. |
+| — | — | — | Note: Many endpoints do not support arbitrary `limit` values; rely on server defaults and use `cursor` to paginate. |
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 90972da and 171ee10.

📒 Files selected for processing (3)
  • API.md (1 hunks)
  • TESTING.md (1 hunks)
  • tests/integration/test_direct_api_tools_integration.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/integration/test_direct_api_tools_integration.py
  • TESTING.md
🧰 Additional context used
📓 Path-based instructions (1)
API.md

📄 CodeRabbit inference engine (.cursor/rules/800-api-documentation-guidelines.mdc)

API.md: Whenever a new MCP tool is added or an existing one is modified, its corresponding REST API endpoint in API.md MUST be added or updated.
Each endpoint documentation MUST follow the exact Markdown structure specified for consistency, including the heading format, parameter table, and example request.
The heading should be the human-readable tool name, with the function name in backticks.
The parameter table must clearly distinguish between required and optional parameters.
The curl example should demonstrate a realistic use case, including optional parameters where applicable.

Update REST API documentation in API.md for each new or updated endpoint, following the API documentation guidelines.

Files:

  • API.md
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-29T04:02:27.836Z
Learning: Applies to api/routes.py : Expose each new tool via the REST API by creating a wrapper endpoint in api/routes.py and registering the route under the /v1/ prefix.
🪛 markdownlint-cli2 (0.17.2)
API.md

497-497: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


506-506: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Docker build and docker push
  • GitHub Check: Run Integration Tests
🔇 Additional comments (1)
API.md (1)

491-496: Section title and route declaration look correct.

Matches guideline: human-readable heading with function name in backticks and clear route path.

@akolotov akolotov merged commit ee00d1b into main Aug 27, 2025
7 of 8 checks passed
@akolotov akolotov deleted the codex/implement-direct_api_call-tool branch August 27, 2025 16:27
@akolotov akolotov changed the title Remove unsupported limit parameter from direct API pagination tests Added direct_api_call tool Aug 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Introduce direct_api_call tool for generic Blockscout endpoint access
1 participant