Skip to content

Implement version-aware instructions delivery #167

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

akolotov
Copy link
Collaborator

@akolotov akolotov commented Jul 18, 2025

Summary

  • allow structured InstructionsData in ToolResponse
  • provide protocol version constant
  • refactor instruction formatting into dedicated modules
  • return XML or structured instructions depending on client version
  • update server to use new formatters
  • document and test new formatting utilities
  • update docs for instruction delivery behavior

Closes #166


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

Summary by CodeRabbit

  • New Features

    • Added version-aware instruction delivery, providing structured instructions for modern clients and XML-formatted instructions for legacy clients.
    • Introduced new XML formatting utilities for server instructions.
  • Bug Fixes

    • Improved handling of the instructions field to support both structured and XML formats based on client version.
  • Documentation

    • Updated documentation to clarify version-aware instruction delivery, response structure, and new formatting utilities.
  • Tests

    • Added and updated tests to cover version-aware instruction responses and XML formatting functions.

Copy link

coderabbitai bot commented Jul 18, 2025

Walkthrough

This change introduces version-aware instruction delivery for the __get_instructions__ tool. It updates the response to place instructions in the instructions field, returning either a structured InstructionsData object for modern clients or a list of XML-formatted strings for legacy clients, based on the protocol version. Documentation, model annotations, and tests are updated accordingly. New XML formatting utilities and corresponding tests are added.

Changes

File(s) Change Summary
blockscout_mcp_server/tools/get_instructions.py Adds version-aware logic for instruction formatting, new helper, updates return type and placement of instructions.
blockscout_mcp_server/models.py
blockscout_mcp_server/tools/common.py
Expands instructions type to `list[str]
blockscout_mcp_server/formatting/xml_formatters.py Adds XML formatting functions for various instruction/rule sets.
blockscout_mcp_server/formatting/instruction_formatters.py Adds function to aggregate all XML instruction formatters into a list.
blockscout_mcp_server/formatting/__init__.py Adds module docstring for formatting utilities.
blockscout_mcp_server/constants.py Adds MODERN_PROTOCOL_VERSION_THRESHOLD constant for protocol version checks.
blockscout_mcp_server/server.py Switches to using the new XML instruction formatter for composed instructions.
tests/tools/test_get_instructions.py Adds and refactors tests for version-aware instruction delivery and protocol version logic.
tests/api/test_routes.py Updates /v1/get_instructions endpoint test to expect new response structure.
tests/formatting/test_instruction_formatters.py
tests/formatting/test_xml_formatters.py
Adds unit tests for XML formatting functions and aggregator.
tests/formatting/__init__.py Adds docstring for formatting tests.
AGENTS.md
API.md
SPEC.md
Updates documentation for version-aware instructions, response structure, and model changes.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server
    participant get_instructions
    participant Formatting

    Client->>Server: /v1/get_instructions (with protocolVersion)
    Server->>get_instructions: Call __get_instructions__(ctx)
    get_instructions->>get_instructions: Extract protocolVersion from ctx
    alt protocolVersion >= threshold
        get_instructions->>get_instructions: Prepare InstructionsData object
        get_instructions-->>Server: ToolResponse { data: {}, instructions: InstructionsData }
    else protocolVersion < threshold or missing
        get_instructions->>Formatting: format_all_instructions_as_xml_strings()
        Formatting-->>get_instructions: list of XML strings
        get_instructions-->>Server: ToolResponse { data: {}, instructions: list[str] }
    end
    Server-->>Client: JSON response with data and instructions
Loading

Possibly related PRs

  • blockscout/mcp-server#159: Introduces structural grouping and XML-tagged sections for instructions, which this PR extends with version-aware delivery and conditional formatting logic.
✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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: 1

🧹 Nitpick comments (3)
blockscout_mcp_server/constants.py (1)

85-86: LGTM! Consider enhancing documentation.

The constant is well-named and serves its purpose for version-aware instruction delivery. The date format is appropriate for protocol versioning.

Consider adding a more descriptive comment explaining what this threshold represents:

+# Protocol version threshold for modern MCP client features
+# Clients with protocol versions >= this date support structured InstructionsData
 MODERN_PROTOCOL_VERSION_THRESHOLD = "2025-06-18"
tests/formatting/test_xml_formatters.py (1)

14-55: Consider adding more comprehensive test coverage.

While these tests verify basic XML structure, consider enhancing them to:

  1. Verify actual content between tags (not just tag presence)
  2. Validate well-formed XML structure
  3. Test that constants like SERVER_VERSION are properly included

For example, enhance the version test:

 def test_format_mcp_server_version_xml():
     version_xml = format_mcp_server_version_xml()
     assert version_xml.startswith("<mcp_server_version>")
     assert version_xml.endswith("</mcp_server_version>")
+    # Verify actual version is included
+    from blockscout_mcp_server.constants import SERVER_VERSION
+    assert f"<mcp_server_version>{SERVER_VERSION}</mcp_server_version>" == version_xml
SPEC.md (1)

371-371: Fix markdown formatting for consistency.

The static analysis tool flagged emphasis used instead of headings.

-**Protocol Version Detection**
+##### Protocol Version Detection
 - Inspect `ctx.session.client_params.protocolVersion` when available
 - Versions `>= 2025-06-18` are treated as modern clients
 - Older or missing versions default to legacy behavior

-**Response Format Selection**
+##### Response Format Selection
 1. **Modern Clients**: return an empty `data` object and place a structured `InstructionsData` in the `instructions` field
 2. **Legacy Clients**: return an empty `data` object and provide XML-formatted instruction strings in `instructions`

Also applies to: 378-378

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)
tests/tools/test_get_instructions.py (2)

82-94: Legacy client test needs more comprehensive validation.

While the test correctly sets up a legacy client scenario with an older protocolVersion, the assertions are minimal and don't verify the complete structure of the XML instruction strings.

Consider enhancing the test to validate more aspects of the legacy response:

@pytest.mark.asyncio
async def test_get_instructions_legacy_client(mock_ctx):
    """Legacy clients receive XML instruction strings."""
    mock_ctx.session = MagicMock()
    mock_ctx.session.client_params = MagicMock()
    mock_ctx.session.client_params.protocolVersion = "2024-01-01"

    result = await __get_instructions__(ctx=mock_ctx)

    assert isinstance(result.data, EmptyData)
    assert isinstance(result.instructions, list)
+   assert len(result.instructions) > 0
    assert any("<error_handling_rules>" in s for s in result.instructions)
+   assert any("<chain_id_guidance>" in s for s in result.instructions)
+   assert any("<pagination_rules>" in s for s in result.instructions)
+   
+   # Verify progress tracking
+   assert mock_ctx.report_progress.call_count == 2

96-104: REST API test could be more explicit about the scenario.

The test correctly validates that clients without a protocolVersion parameter receive XML instruction strings, but the test name and documentation could be clearer about what "REST API" means in this context.

Consider improving the test clarity:

@pytest.mark.asyncio
-async def test_get_instructions_rest_api(mock_ctx):
-    """No protocol version behaves like legacy client."""
+async def test_get_instructions_no_protocol_version(mock_ctx):
+    """Clients without protocolVersion parameter receive XML instruction strings like legacy clients."""
    result = await __get_instructions__(ctx=mock_ctx)

    assert isinstance(result.data, EmptyData)
    assert isinstance(result.instructions, list)
    assert any("<chain_id_guidance>" in s for s in result.instructions)
+   
+   # Verify progress tracking
+   assert mock_ctx.report_progress.call_count == 2
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7bb8f6 and c012ccf.

📒 Files selected for processing (4)
  • blockscout_mcp_server/models.py (2 hunks)
  • blockscout_mcp_server/tools/get_instructions.py (5 hunks)
  • tests/api/test_routes.py (2 hunks)
  • tests/tools/test_get_instructions.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • blockscout_mcp_server/models.py
  • tests/api/test_routes.py
  • blockscout_mcp_server/tools/get_instructions.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • .cursor/rules/000-role-and-task.mdc
  • .cursor/rules/010-implementation-rules.mdc
  • .cursor/rules/300-ruff-lint-and-format.mdc
tests/tools/test_*.py

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • .cursor/rules/200-development-testing-workflow.mdc
tests/tools/*.py

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • .cursor/rules/210-unit-testing-guidelines.mdc
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
PR: blockscout/mcp-server#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-04T20:16:41.182Z
Learning: Read the project technical description in `SPEC.md` to clearly understand how the MCP server communicates with counterparties and important aspects of the server implementation before responding to a user request
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/130-version-management.mdc:0-0
Timestamp: 2025-07-04T20:25:11.277Z
Learning: Follow semantic versioning (MAJOR.MINOR.PATCH) when choosing version numbers for the MCP server.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-04T20:16:41.182Z
Learning: Read `.cursor/rules/110-new-mcp-tool.mdc` for instructions on adding a new MCP tool to the server or modifying an existing one before responding to a user request
Learnt from: akolotov
PR: blockscout/mcp-server#142
File: blockscout_mcp_server/api/routes.py:242-258
Timestamp: 2025-07-10T19:21:50.271Z
Learning: The user akolotov prefers a unified tool registration approach where a single function registers tools in both MCP and REST API instead of having separate registration points in blockscout_mcp_server/server.py and blockscout_mcp_server/api/routes.py. This would eliminate duplication and reduce maintenance burden.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/130-version-management.mdc:0-0
Timestamp: 2025-07-04T20:25:11.277Z
Learning: Applies to blockscout_mcp_server/__init__.py : When updating the version of the MCP server, update the `__version__` variable in `blockscout_mcp_server/__init__.py`.
tests/tools/test_get_instructions.py (19)
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-04T20:28:28.716Z
Learning: Applies to tests/integration/test_*_integration.py : Add integration tests in tests/integration/test_{tool_module}_integration.py when adding or modifying tool functions that interact with live APIs
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-04T20:30:09.292Z
Learning: Applies to tests/tools/*.py : When testing tools that transform a list of items, programmatically generate the `expected_result` from the `mock_api_response` instead of writing out large, repetitive expected results.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-04T20:28:28.716Z
Learning: Applies to tests/integration/test_common_helpers.py : Add integration tests in tests/integration/test_common_helpers.py when modifying helper functions in tools/common.py
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-04T20:30:09.292Z
Learning: Applies to tests/tools/*.py : Ensure all external API calls are properly mocked using `unittest.mock.patch` and `AsyncMock`.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/220-integration-testing-guidelines.mdc:0-0
Timestamp: 2025-07-04T20:31:56.480Z
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 should validate data extraction and schema against live API responses.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-04T20:30:09.292Z
Learning: Applies to tests/tools/*.py : For tools returning 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.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-04T20:28:28.716Z
Learning: Applies to tests/tools/test_*.py : Create or update the appropriate unit test file when adding or modifying tool functions: tests/tools/test_{tool_module}.py
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-04T20:28:28.716Z
Learning: Applies to tests/test_server.py : Create or update the unit test file tests/test_server.py when adding or modifying server functionality
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-04T20:30:09.292Z
Learning: Applies to tests/tools/*.py : Group related tests using descriptive class names or clear function naming patterns.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/200-development-testing-workflow.mdc:0-0
Timestamp: 2025-07-04T20:28:28.716Z
Learning: Run integration tests (pytest -m integration) when you have added or modified any existing MCP tool function, modified helper functions in tools/common.py, added or changed any integration test, or changed data extraction or transformation logic
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-04T20:22:01.665Z
Learning: Applies to blockscout_mcp_server/tools/*.py : All tools MUST return a strongly-typed ToolResponse[YourDataModel] instead of generic ToolResponse[dict].
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-04T20:30:09.292Z
Learning: Applies to tests/tools/*.py : Use the `mock_ctx` pytest fixture from `tests/conftest.py` for mocking the MCP Context object in tests; do not create a manual MagicMock for the context within your test functions.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-04T20:22:01.665Z
Learning: Applies to blockscout_mcp_server/tools/*.py : Avoid returning ToolResponse[dict] or ToolResponse[Any]; always prefer specific ToolResponse[YourDataModel].
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-04T20:30:09.292Z
Learning: Applies to tests/tools/*.py : 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.).
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-04T20:22:01.665Z
Learning: Applies to blockscout_mcp_server/tools/*.py : All tools MUST return a standardized ToolResponse[YourDataModel] object using the build_tool_response helper.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-04T20:30:09.292Z
Learning: Applies to tests/tools/*.py : Always verify the number of calls to `mock_ctx.report_progress` in your assertions to ensure progress tracking is tested.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-04T20:22:01.665Z
Learning: Applies to blockscout_mcp_server/tools/*.py : For tools that query Blockscout API, use dynamic chain resolution via get_blockscout_base_url and make_blockscout_request.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/210-unit-testing-guidelines.mdc:0-0
Timestamp: 2025-07-04T20:30:09.292Z
Learning: Applies to tests/tools/*.py : Assert that mocked API helper functions (such as `make_blockscout_request`) are called exactly once with the correct `api_path` and `params` in your tests.
Learnt from: CR
PR: blockscout/mcp-server#0
File: .cursor/rules/110-new-mcp-tool.mdc:0-0
Timestamp: 2025-07-04T20:22:01.665Z
Learning: Applies to blockscout_mcp_server/tools/*.py : When making multiple independent API calls, use asyncio.gather with return_exceptions=True and handle exceptions appropriately.
🧬 Code Graph Analysis (1)
tests/tools/test_get_instructions.py (3)
blockscout_mcp_server/models.py (3)
  • EmptyData (12-15)
  • InstructionsData (60-69)
  • ToolResponse (288-315)
blockscout_mcp_server/tools/get_instructions.py (1)
  • is_modern_protocol_version (30-37)
tests/conftest.py (1)
  • mock_ctx (8-13)
🔇 Additional comments (3)
tests/tools/test_get_instructions.py (3)

1-13: Import statements follow proper structure and include all necessary dependencies.

The imports are properly organized at the top of the file and include the new models and helper function needed for the version-aware instruction delivery tests.


17-72: Comprehensive test coverage for modern client behavior with proper mocking.

The test correctly validates that modern clients (with protocolVersion >= "2025-06-18") receive structured InstructionsData in the instructions field and EmptyData in the data field. The mocking strategy is thorough, patching all relevant constants and properly setting up the mock context with session parameters. Progress tracking assertions are included as required by the coding guidelines.


74-80: Well-structured unit test for the helper function.

The test validates the is_modern_protocol_version helper function with appropriate edge cases including valid dates above/below the threshold, None values, and invalid date strings. This ensures the version comparison logic works correctly.

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.

Rework __get_instructions__ to Improve Agent Compliance
1 participant