You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2026/07/19 12:52:48 UTC

(shardingsphere) branch master updated: Fix MCP E2E metadata search response pagination (#39185)

This is an automated email from the ASF dual-hosted git repository.

terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 8b15b93e49c Fix MCP E2E metadata search response pagination (#39185)
8b15b93e49c is described below

commit 8b15b93e49cbf675c2274ef01bef3b0b62259f08
Author: Liang Zhang <zh...@apache.org>
AuthorDate: Sun Jul 19 20:52:42 2026 +0800

    Fix MCP E2E metadata search response pagination (#39185)
    
    Preserve complete metadata search pages in model-facing responses while
    retaining compact non-search responses and canonical pagination actions.
---
 .../LLMMCPModelFacingToolResponseFormatter.java    |  6 ++-
 ...LLMMCPModelFacingToolResponseFormatterTest.java | 55 ++++++++++++++++++++++
 2 files changed, 60 insertions(+), 1 deletion(-)

diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatter.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatter.java
index a63c0019013..b1cc13d66f3 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatter.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatter.java
@@ -22,6 +22,7 @@ import lombok.NoArgsConstructor;
 import org.apache.shardingsphere.infra.util.json.JsonUtils;
 import org.apache.shardingsphere.mcp.support.protocol.MCPModelFacingPayloadContract;
 import org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
+import org.apache.shardingsphere.mcp.support.protocol.MCPResponseMode;
 
 import java.util.Collection;
 import java.util.LinkedHashMap;
@@ -33,6 +34,8 @@ import java.util.Objects;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 final class LLMMCPModelFacingToolResponseFormatter {
     
+    private static final int COMPACT_ITEM_LIMIT = 5;
+    
     private static final List<String> GENERAL_FIELD_NAMES = List.of(
             "response_mode", "error_code", "error_id", "summary", "message", "recovery_category", "result_kind", "status", "statement_type", "normalized_sql", "rows", "row_objects",
             "returned_row_count", "plan_id", "workflow_resource");
@@ -112,7 +115,8 @@ final class LLMMCPModelFacingToolResponseFormatter {
             return;
         }
         List<Map<String, Object>> compactItems = new LinkedList<>();
-        for (Map<String, Object> each : items.subList(0, Math.min(items.size(), 5))) {
+        int visibleItemCount = MCPResponseMode.SEARCH.equals(source.get("response_mode")) ? items.size() : Math.min(items.size(), COMPACT_ITEM_LIMIT);
+        for (Map<String, Object> each : items.subList(0, visibleItemCount)) {
             Map<String, Object> compactItem = new LinkedHashMap<>(8, 1F);
             copyIfPresent(each, compactItem, "database");
             copyIfPresent(each, compactItem, "schema");
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatterTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatterTest.java
index 5b340754819..8370aaf0186 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatterTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPModelFacingToolResponseFormatterTest.java
@@ -19,10 +19,12 @@ package org.apache.shardingsphere.test.e2e.mcp.llm.conversation;
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import org.apache.shardingsphere.infra.util.json.JsonUtils;
+import org.apache.shardingsphere.mcp.support.protocol.MCPResponseMode;
 import org.junit.jupiter.api.Test;
 
 import java.util.List;
 import java.util.Map;
+import java.util.stream.IntStream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -63,6 +65,51 @@ class LLMMCPModelFacingToolResponseFormatterTest {
                 Map.of("name", "order_id")))));
     }
     
+    @Test
+    void assertFormatWithCompactListItems() {
+        Map<String, Object> actual = format(Map.of("response_mode", MCPResponseMode.LIST, "items", createVerboseItems(6)));
+        assertThat(actual, is(Map.of("response_mode", MCPResponseMode.LIST, "items", createCompactItems(5))));
+    }
+    
+    @Test
+    void assertFormatWithCompleteSearchItems() {
+        Map<String, Object> actual = format(Map.of(
+                "response_mode", MCPResponseMode.SEARCH,
+                "items", createVerboseItems(100),
+                "count", 100,
+                "truncated", false));
+        assertThat(actual, is(Map.of(
+                "response_mode", MCPResponseMode.SEARCH,
+                "items", createCompactItems(100),
+                "count", 100,
+                "truncated", false)));
+    }
+    
+    @Test
+    void assertFormatWithPaginatedSearchItems() {
+        Map<String, Object> nextAction = Map.of(
+                "order", 1,
+                "type", "tool_call",
+                "title", "Call database_gateway_search_metadata",
+                "tool_name", "database_gateway_search_metadata",
+                "arguments", Map.of("database", "logic_db", "object_types", List.of("table"), "limit", 100, "offset", 100),
+                "reason", "Read the next deterministically ordered metadata search page.");
+        Map<String, Object> actual = format(Map.of(
+                "response_mode", MCPResponseMode.SEARCH,
+                "items", createVerboseItems(100),
+                "count", 100,
+                "total_match_count", 101,
+                "truncated", true,
+                "next_actions", List.of(nextAction)));
+        assertThat(actual, is(Map.of(
+                "response_mode", MCPResponseMode.SEARCH,
+                "items", createCompactItems(100),
+                "count", 100,
+                "total_match_count", 101,
+                "truncated", true,
+                "next_actions", List.of(nextAction))));
+    }
+    
     @Test
     void assertFormatWithPrompts() {
         Map<String, Object> actual = format(Map.of("prompts", List.of(Map.of("name", "inspect_metadata"), Map.of("description", "ignored"))));
@@ -161,4 +208,12 @@ class LLMMCPModelFacingToolResponseFormatterTest {
         return JsonUtils.fromJsonString(LLMMCPModelFacingToolResponseFormatter.format(value), new TypeReference<>() {
         });
     }
+    
+    private List<Map<String, Object>> createVerboseItems(final int count) {
+        return IntStream.range(0, count).mapToObj(each -> Map.<String, Object>of("name", "item_" + each, "ignored", "value")).toList();
+    }
+    
+    private List<Map<String, Object>> createCompactItems(final int count) {
+        return IntStream.range(0, count).mapToObj(each -> Map.<String, Object>of("name", "item_" + each)).toList();
+    }
 }