You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .cursor/rules/110-new-mcp-tool.mdc
+74-24Lines changed: 74 additions & 24 deletions
Original file line number
Diff line number
Diff line change
@@ -353,15 +353,50 @@ return build_tool_response(
353
353
354
354
#### 5. Handling Pagination with Opaque Cursors (`return_type: ToolResponse[list[dict]]`)
355
355
356
-
For tools that return paginated data, do not expose individual pagination parameters (like `page`, `offset`, `items_count`) in the tool's signature. Instead, use a single, opaque `cursor` string. This improves robustness and saves LLM context. The implementation involves both handling an incoming cursor and generating the next one.
356
+
For tools that return paginated data, do not expose individual pagination parameters (like `page`, `offset`, `items_count`) in the tool's signature. Instead, use a single, opaque `cursor` string. This improves robustness and saves LLM context.
357
+
358
+
**Context Conservation Strategy:**
359
+
Many blockchain APIs return large datasets (50+ items per page) that would overwhelm LLM context. To balance network efficiency with context conservation, tools should:
360
+
361
+
- Fetch larger pages from APIs (typically 50 items) for network efficiency
362
+
- Return smaller slices to the LLM (typically 10-20 items) to conserve context
363
+
- Generate pagination objects that allow the LLM to request additional pages when needed
357
364
358
365
**A. Handling the Incoming Cursor:**
359
366
Your tool should accept an optional `cursor` argument. If it's provided, use the `apply_cursor_to_params` helper from `tools/common.py`. This helper centralizes the logic for decoding the cursor and handling potential `InvalidCursorError` exceptions, raising a user-friendly `ValueError` automatically.
360
367
361
368
**B. Generating Structured Pagination:**
362
-
In your response, check for `next_page_params` from the API. If they exist, create `PaginationInfo` and `NextCallInfo` objects with the structured parameters for the next call.
369
+
**ALWAYS use the `create_items_pagination` helper** from `tools/common.py` instead of manually creating pagination objects. This function implements the response slicing strategy described above, while also ensuring consistency and handling edge cases properly.
370
+
371
+
**C. Page Size Configuration:**
372
+
For each new paginated tool, you must add a dedicated page size configuration variable:
373
+
374
+
1. **Add to `blockscout_mcp_server/config.py`**:
375
+
376
+
```python
377
+
class ServerConfig(BaseSettings):
378
+
# Existing page sizes
379
+
nft_page_size: int = 10
380
+
logs_page_size: int = 10
381
+
advanced_filters_page_size: int = 10
382
+
383
+
# Add your new page size
384
+
my_tool_page_size: int = 15 # Adjust based on typical item size
385
+
```
386
+
387
+
2. **Add to `.env.example`**:
388
+
389
+
```shell
390
+
BLOCKSCOUT_MY_TOOL_PAGE_SIZE=15
391
+
```
392
+
393
+
3. **Add to `Dockerfile`**:
394
+
395
+
```dockerfile
396
+
ENV BLOCKSCOUT_MY_TOOL_PAGE_SIZE="15"
397
+
```
363
398
364
-
**C. Tool Description Guidelines:**
399
+
**D. Tool Description Guidelines:**
365
400
For paginated tools, **MUST** include this exact notice in the docstring: `**SUPPORTS PAGINATION**: If response includes 'pagination' field, use the provided next_call to get additional pages.`
366
401
367
402
**Complete Example Pattern:**
@@ -372,11 +407,25 @@ from pydantic import Field
372
407
from blockscout_mcp_server.tools.common import (
373
408
make_blockscout_request,
374
409
get_blockscout_base_url,
375
-
encode_cursor,
376
410
apply_cursor_to_params,
377
-
build_tool_response
411
+
build_tool_response,
412
+
create_items_pagination,
378
413
)
379
-
from blockscout_mcp_server.models import ToolResponse, PaginationInfo, NextCallInfo
414
+
from blockscout_mcp_server.models import ToolResponse
415
+
from blockscout_mcp_server.config import config
416
+
417
+
def extract_cursor_params(item: dict) -> dict:
418
+
"""Extract cursor parameters from an item for pagination continuation.
419
+
420
+
This function determines which fields from the last item should be used
421
+
as cursor parameters for the next page request. The returned dictionary
#### 6. Simplifying Address Objects to Save Context (`return_type: ToolResponse[dict]`)
421
471
422
472
**Rationale:** Many Blockscout API endpoints return addresses as complex JSON objects containing the hash, name, tags, etc. To conserve LLM context and encourage compositional tool use, we must simplify these objects into a single address string. If the AI needs more details about an address, it should be guided to use the dedicated `get_address_info` tool.
Copy file name to clipboardExpand all lines: .cursor/rules/210-unit-testing-guidelines.mdc
+33-2Lines changed: 33 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,37 @@ alwaysApply: false
7
7
8
8
This document provides detailed guidelines for writing effective unit tests for MCP tool functions and related components.
9
9
10
+
## **HIGH PRIORITY: Keep Unit Tests Simple and Focused**
11
+
12
+
**Each unit test must be narrow and specific.** A single test should verify one specific behavior or scenario. If a test attempts to cover multiple scenarios or different groups of input parameters, **split it into separate tests**.
13
+
14
+
**Simple tests are:**
15
+
16
+
- Easier to understand and maintain
17
+
- Faster to debug when they fail
18
+
- More reliable and less prone to false positives
19
+
- Better at pinpointing the exact cause of failures
20
+
21
+
**Example - Split complex tests:**
22
+
23
+
```python
24
+
# BAD: One test covering multiple scenarios
25
+
def test_lookup_token_complex():
26
+
# Tests both success and error cases
27
+
# Tests multiple input parameter combinations
28
+
# Hard to debug when it fails
29
+
30
+
# GOOD: Separate focused tests
31
+
def test_lookup_token_success():
32
+
# Tests only the success scenario
33
+
34
+
def test_lookup_token_invalid_symbol():
35
+
# Tests only invalid symbol error case
36
+
37
+
def test_lookup_token_network_error():
38
+
# Tests only network error handling
39
+
```
40
+
10
41
## Key Testing Patterns & Guidelines
11
42
12
43
### A. Use the `mock_ctx` Fixture
@@ -16,6 +47,7 @@ A reusable `pytest` fixture named `mock_ctx` is defined in `tests/conftest.py`.
16
47
**DO NOT** create a manual `MagicMock` for the context within your test functions.
17
48
18
49
**Correct Usage:**
50
+
19
51
```python
20
52
import pytest
21
53
@@ -38,15 +70,14 @@ For tools that return a `ToolResponse` object containing structured data, **DO N
38
70
39
71
However, the approach depends on the complexity of the tool.
40
72
41
-
42
-
43
73
### C. Handling Repetitive Data in Assertions (DAMP vs. DRY)
44
74
45
75
When testing tools that transform a list of items (e.g., `lookup_token_by_symbol`), explicitly writing out the entire `expected_result` can lead to large, repetitive, and hard-to-maintain test code.
46
76
47
77
In these cases, it is better to **programmatically generate the `expected_result`** from the `mock_api_response`. This keeps the test maintainable while still explicitly documenting the transformation logic itself.
48
78
49
79
**Correct Usage:**
80
+
50
81
```python
51
82
import copy
52
83
from blockscout_mcp_server.models import ToolResponse
0 commit comments