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 8bbf402e53b Refine MCP descriptor and workflow test helpers (#38976)
8bbf402e53b is described below
commit 8bbf402e53b4db74e24faddbecb199bf189d6cef
Author: Liang Zhang <[email protected]>
AuthorDate: Wed Jul 1 22:45:59 2026 +0800
Refine MCP descriptor and workflow test helpers (#38976)
* Fix documentation accuracy and consistency
- align DistSQL syntax docs, return columns, and examples with current
source
- normalize SQL and server error code documentation
- fix MCP resources, management metadata docs, links, versions, and typos
* Fix documentation accuracy and consistency
- align DistSQL syntax docs, return columns, and examples with current
source
- normalize SQL and server error code documentation
- fix MCP resources, management metadata docs, links, versions, and typos
* Refactor MCP workflow support duplication
Centralize repeated workflow validation, identifier checks, DistSQL rule
query fallback, and MCP error/recovery mapping while preserving existing
behavior. Reuse shared helpers across MCP feature planning/validation services
and simplify duplicated MCP E2E test setup helpers.
* Refine MCP descriptor and workflow test helpers
Extract shared MCP tool schema lookup helpers into
MCPToolDescriptorValidationUtils and reuse them from descriptor validators.
Simplify repeated MCP tool specification, JDBC metadata loader, and workflow
execution test setup with focused test helpers while preserving existing
test
coverage and behavior.
---
.../AbstractMCPToolSpecificationFactoryTest.java | 24 +++-
.../tool/MCPToolSpecificationFactoryTest.java | 83 ++++--------
.../descriptor/CoreToolDescriptorValidator.java | 35 +----
.../workflow/WorkflowExecutionServiceTest.java | 144 +++++++--------------
.../descriptor/MCPDescriptorCatalogValidator.java | 20 +--
.../MCPToolDescriptorValidationUtils.java | 44 +++++++
.../metadata/jdbc/MCPJdbcMetadataLoaderTest.java | 66 +++++-----
.../MCPToolDescriptorValidationUtilsTest.java | 32 +++++
8 files changed, 212 insertions(+), 236 deletions(-)
diff --git
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/AbstractMCPToolSpecificationFactoryTest.java
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/AbstractMCPToolSpecificationFactoryTest.java
index 56d99424d52..1b5f3606823 100644
---
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/AbstractMCPToolSpecificationFactoryTest.java
+++
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/AbstractMCPToolSpecificationFactoryTest.java
@@ -122,6 +122,25 @@ abstract class AbstractMCPToolSpecificationFactoryTest {
return result;
}
+ protected SyncToolSpecification createToolSpecification(final String
activeTransport) {
+ return createToolSpecification(createRuntimeContext(activeTransport));
+ }
+
+ protected SyncToolSpecification createToolSpecification(final
MCPRuntimeContext runtimeContext) {
+ return new
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications().getFirst();
+ }
+
+ protected McpSyncServerExchange createExchange() {
+ McpSyncServerExchange result = mock(McpSyncServerExchange.class);
+ when(result.sessionId()).thenReturn("session-id");
+ return result;
+ }
+
+ protected CallToolResult callTool(final SyncToolSpecification
toolSpecification, final McpSyncServerExchange exchange, final String toolName,
+ final Map<String, Object> arguments) {
+ return toolSpecification.callHandler().apply(exchange, new
CallToolRequest(toolName, arguments));
+ }
+
protected void assertStructuredFallback(final CallToolResult actual, final
String expectedReason, final boolean expectedFormMode, final boolean
expectedUrlMode,
final String expectedInteraction) {
Map<String, Object> actualPayload = getStructuredContent(actual);
@@ -160,10 +179,7 @@ abstract class AbstractMCPToolSpecificationFactoryTest {
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
MCPRuntimeContext runtimeContext = mock(MCPRuntimeContext.class,
RETURNS_DEEP_STUBS);
when(runtimeContext.getSessionManager().getTransactionResourceManager().getRuntimeDatabases()).thenReturn(Collections.emptyMap());
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications().get(0);
- McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
- when(exchange.sessionId()).thenReturn("session-id");
- return actualSpecification.callHandler().apply(exchange, new
CallToolRequest(toolName, Map.of()));
+ return callTool(createToolSpecification(runtimeContext),
createExchange(), toolName, Map.of());
}
}
diff --git
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/MCPToolSpecificationFactoryTest.java
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/MCPToolSpecificationFactoryTest.java
index 5cfef51658c..3bc9b54783a 100644
---
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/MCPToolSpecificationFactoryTest.java
+++
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/tool/MCPToolSpecificationFactoryTest.java
@@ -140,9 +140,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new MCPMapResponse(Map.of("status", "ok"));
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createToolDescriptor("database_gateway_search_metadata"));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
- McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
- when(exchange.sessionId()).thenReturn("session-id");
+ SyncToolSpecification actualSpecification =
createToolSpecification("stdio");
+ McpSyncServerExchange exchange = createExchange();
CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("database_gateway_search_metadata", null));
assertThat(actual.structuredContent(), is(Map.of("status", "ok")));
assertThat(((TextContent) actual.content().getFirst()).text(),
is("{\"status\":\"ok\"}"));
@@ -155,10 +154,7 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
try (MockedStatic<ToolDefinitionRegistry> mockedToolDefinitionRegistry
= mockStatic(ToolDefinitionRegistry.class)) {
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createToolDescriptor("database_gateway_search_metadata"));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of("query", "foo_query"), new MCPErrorResponse(""));
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
- McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
- when(exchange.sessionId()).thenReturn("session-id");
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("database_gateway_search_metadata", Map.of("query",
"foo_query")));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
createExchange(), "database_gateway_search_metadata", Map.of("query",
"foo_query"));
@SuppressWarnings("unchecked")
Map<String, Object> actualPayload = (Map<String, Object>)
actual.structuredContent();
assertThat(actualPayload.get("response_mode"), is("recovery"));
@@ -262,10 +258,7 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
try (MockedStatic<ToolDefinitionRegistry> mockedToolDefinitionRegistry
= mockStatic(ToolDefinitionRegistry.class)) {
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createToolDescriptor("fixture_ping"));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), new MCPMapResponse(Map.of("status", "ok")));
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
- McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
- when(exchange.sessionId()).thenReturn("session-id");
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("fixture_ping", Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
createExchange(), "fixture_ping", Map.of());
assertThat(actual.structuredContent(), is(Map.of("status", "ok")));
assertThat(((TextContent) actual.content().getFirst()).text(),
is("{\"status\":\"ok\"}"));
}
@@ -278,10 +271,9 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
mockedToolDefinitionRegistry.when(() ->
ToolDefinitionRegistry.getToolDefinition("database_gateway_search_metadata")).thenThrow(UnsupportedToolException.class);
MCPRuntimeContext runtimeContext = mock(MCPRuntimeContext.class,
RETURNS_DEEP_STUBS);
when(runtimeContext.getSessionManager().getTransactionResourceManager().getRuntimeDatabases()).thenReturn(Collections.emptyMap());
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications().getFirst();
- McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
- when(exchange.sessionId()).thenReturn("session-id");
- McpError actual = assertThrows(McpError.class, () ->
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("database_gateway_search_metadata", Map.of())));
+ SyncToolSpecification actualSpecification =
createToolSpecification(runtimeContext);
+ McpSyncServerExchange exchange = createExchange();
+ McpError actual = assertThrows(McpError.class, () ->
callTool(actualSpecification, exchange, "database_gateway_search_metadata",
Map.of()));
assertThat(actual.getJsonRpcError().code(),
is(McpSchema.ErrorCodes.INVALID_PARAMS));
assertThat(actual.getJsonRpcError().message(), is("Unsupported
tool `database_gateway_search_metadata`."));
@SuppressWarnings("unchecked")
@@ -295,10 +287,7 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPRuntimeContext runtimeContext = mock(MCPRuntimeContext.class,
RETURNS_DEEP_STUBS);
when(runtimeContext.getSessionManager().getTransactionResourceManager().getRuntimeDatabases()).thenReturn(Collections.emptyMap());
SyncToolSpecification actualSpecification = findToolSpecification(new
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications(),
"database_gateway_search_metadata");
- McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
- when(exchange.sessionId()).thenReturn("session-id");
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange,
- new CallToolRequest("database_gateway_search_metadata",
Map.of("query", "order", "object_types", List.of("TABLE"))));
+ CallToolResult actual = callTool(actualSpecification,
createExchange(), "database_gateway_search_metadata", Map.of("query", "order",
"object_types", List.of("TABLE")));
@SuppressWarnings("unchecked")
Map<String, Object> actualPayload = (Map<String, Object>)
actual.structuredContent();
Map<?, ?> actualRecovery = (Map<?, ?>) actualPayload.get("recovery");
@@ -317,10 +306,7 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), new MCPMapResponse(Map.of("count", 1)));
MCPRuntimeContext runtimeContext = mock(MCPRuntimeContext.class,
RETURNS_DEEP_STUBS);
when(runtimeContext.getSessionManager().getTransactionResourceManager().getRuntimeDatabases()).thenReturn(Collections.emptyMap());
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications().getFirst();
- McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
- when(exchange.sessionId()).thenReturn("session-id");
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("database_gateway_search_metadata", Map.of()));
+ CallToolResult actual =
callTool(createToolSpecification(runtimeContext), createExchange(),
"database_gateway_search_metadata", Map.of());
@SuppressWarnings("unchecked")
Map<String, Object> actualPayload = (Map<String, Object>)
actual.structuredContent();
assertTrue(String.valueOf(actualPayload.get("message")).contains("database_gateway_search_metadata"));
@@ -351,10 +337,9 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor(toolName));
mockedToolDefinitionRegistry.when(() ->
ToolDefinitionRegistry.dispatch(any(MCPRequestScope.class), eq(toolDefinition),
eq("session-id"), any()))
.thenReturn(clarifyingResponse, plannedResponse);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
McpSyncServerExchange exchange = createElicitationExchange(new
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT,
Map.of("field_1", "foo_display", "field_2", true)),
clientCapabilities);
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName,
Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, toolName, Map.of());
assertThat(actual.structuredContent(), is(Map.of("status",
"planned")));
ArgumentCaptor<McpSchema.ElicitRequest> requestCaptor =
ArgumentCaptor.forClass(McpSchema.ElicitRequest.class);
verify(exchange).createElicitation(requestCaptor.capture());
@@ -389,9 +374,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
createClarifyingQuestion("primary_algorithm_properties.access-token", "string",
false, "Provide access token.")));
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor(toolName));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
McpSyncServerExchange exchange = createElicitationExchange(new
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of()),
createFormAndUrlClientCapabilities());
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName,
Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, toolName, Map.of());
assertStructuredFallback(actual, "url_mode_not_implemented", true,
true, "url_fallback");
assertSanitizedSensitiveFallback(actual);
verify(exchange, never()).createElicitation(any());
@@ -423,9 +407,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new MCPMapResponse(expectedPayload);
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createAmbiguousPlanningToolDescriptor(toolName));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
McpSyncServerExchange exchange = createElicitationExchange(new
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of("field_1",
true)));
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName,
Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, toolName, Map.of());
assertStructuredFallback(actual, "ambiguous_field_binding", true,
false, "structured_fallback");
verify(exchange, never()).createElicitation(any());
}
@@ -439,9 +422,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new MCPMapResponse(expectedPayload);
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor(toolName));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
McpSyncServerExchange exchange =
createUrlOnlyElicitationExchange();
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName,
Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, toolName, Map.of());
assertStructuredFallback(actual, "client_unsupported", false,
true, "structured_fallback");
verify(exchange, never()).createElicitation(any());
}
@@ -454,9 +436,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new
MCPMapResponse(createClarifyingPayload());
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor(toolName));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("http")).createToolSpecifications().getFirst();
McpSyncServerExchange exchange = createElicitationExchange(new
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of("field_1",
"foo_display")));
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName,
Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("http"),
exchange, toolName, Map.of());
assertStructuredFallback(actual, "remote_identity_required", true,
false, "structured_fallback");
verify(exchange, never()).createElicitation(any());
}
@@ -469,9 +450,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new MCPMapResponse(expectedPayload);
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor(toolName));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
McpSyncServerExchange exchange = createElicitationExchange(new
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT,
Map.of("custom_properties.display-name", "foo_display")));
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName,
Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, toolName, Map.of());
assertStructuredFallback(actual, "sensitive_form_blocked", true,
false, "url_fallback");
assertSanitizedSensitiveFallback(actual);
verify(exchange, never()).createElicitation(any());
@@ -485,9 +465,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new MCPMapResponse(expectedPayload);
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor(toolName));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
McpSyncServerExchange exchange = createElicitationExchange(new
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of("field_1",
"foo_display")));
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new CallToolRequest(toolName,
Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, toolName, Map.of());
assertStructuredFallback(actual, expectedReason, true, false,
expectedInteraction);
if ("url_fallback".equals(expectedInteraction)) {
assertSanitizedSensitiveFallback(actual);
@@ -505,9 +484,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
MCPRuntimeContext runtimeContext = mock(MCPRuntimeContext.class,
RETURNS_DEEP_STUBS);
when(runtimeContext.getSessionManager().getTransactionResourceManager().getRuntimeDatabases()).thenReturn(Collections.emptyMap());
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications().getFirst();
McpSyncServerExchange exchange = createElicitationExchange(new
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of()));
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("database_gateway_search_metadata", Map.of()));
+ CallToolResult actual =
callTool(createToolSpecification(runtimeContext), exchange,
"database_gateway_search_metadata", Map.of());
assertThat(actual.structuredContent(), is(expectedPayload));
verify(exchange, never()).createElicitation(any());
}
@@ -522,9 +500,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
MCPRuntimeContext runtimeContext = mock(MCPRuntimeContext.class,
RETURNS_DEEP_STUBS);
when(runtimeContext.getSessionManager().getTransactionResourceManager().getRuntimeDatabases()).thenReturn(Collections.emptyMap());
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(runtimeContext).createToolSpecifications().getFirst();
McpSyncServerExchange exchange = createElicitationExchange(new
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of()));
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("fixture_ping", Map.of()));
+ CallToolResult actual =
callTool(createToolSpecification(runtimeContext), exchange, "fixture_ping",
Map.of());
assertThat(actual.structuredContent(), is(expectedPayload));
verify(exchange, never()).createElicitation(any());
}
@@ -536,10 +513,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new
MCPMapResponse(createClarifyingPayload());
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
- McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
- when(exchange.sessionId()).thenReturn("session-id");
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+ McpSyncServerExchange exchange = createExchange();
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, "database_gateway_plan_encrypt_rule", Map.of());
assertStructuredFallback(actual, "client_unsupported", false,
false, "structured_fallback");
verify(exchange, never()).createElicitation(any());
}
@@ -551,10 +526,9 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new
MCPMapResponse(createClarifyingPayload());
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
McpSchema.ClientCapabilities clientCapabilities = new
McpSchema.ClientCapabilities(Collections.emptyMap(), null, null, null);
McpSyncServerExchange exchange = createElicitationExchange(new
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of()),
clientCapabilities);
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, "database_gateway_plan_encrypt_rule", Map.of());
assertStructuredFallback(actual, "client_unsupported", false,
false, "structured_fallback");
verify(exchange, never()).createElicitation(any());
}
@@ -603,9 +577,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new
MCPMapResponse(createClarifyingPayload());
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
McpSyncServerExchange exchange =
createThrowingElicitationExchange();
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, "database_gateway_plan_encrypt_rule", Map.of());
assertStructuredFallback(actual, "elicitation_failed", true,
false, "structured_fallback");
verify(exchange).createElicitation(any());
}
@@ -626,13 +599,12 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new
MCPMapResponse(createClarifyingPayload());
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
McpSyncServerExchange exchange = createElicitationExchange(new
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of("field_1",
"foo_display", "field_2", true)));
when(exchange.createElicitation(any())).thenAnswer(invocation -> {
clock.advance(Duration.ofMinutes(11L));
return new
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of("field_1",
"foo_display", "field_2", true));
});
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, "database_gateway_plan_encrypt_rule", Map.of());
assertStructuredFallback(actual, "stale_elicitation", true, false,
"structured_fallback");
mockedToolDefinitionRegistry.verify(() ->
ToolDefinitionRegistry.dispatch(any(MCPRequestScope.class), eq(toolDefinition),
eq("session-id"), eq(createElicitedArguments())), never());
}
@@ -647,9 +619,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new MCPMapResponse(expectedPayload);
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
McpSyncServerExchange exchange = createElicitationExchange(new
McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, elicitedContent));
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, "database_gateway_plan_encrypt_rule", Map.of());
assertStructuredFallback(actual, "invalid_elicited_content", true,
false, "structured_fallback");
verify(exchange).createElicitation(any());
mockedToolDefinitionRegistry.verify(() ->
ToolDefinitionRegistry.dispatch(any(MCPRequestScope.class), eq(toolDefinition),
eq("session-id"), eq(createElicitedArguments())), never());
@@ -661,9 +632,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new
MCPMapResponse(createClarifyingPayload());
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
McpSyncServerExchange exchange =
createElicitationExchange(elicitedResult);
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, "database_gateway_plan_encrypt_rule", Map.of());
assertStructuredFallback(actual, expectedReason, true, false,
"structured_fallback");
verify(exchange).createElicitation(any());
mockedToolDefinitionRegistry.verify(() ->
ToolDefinitionRegistry.dispatch(any(MCPRequestScope.class), eq(toolDefinition),
eq("session-id"), eq(createElicitedArguments())), never());
@@ -763,9 +733,8 @@ class MCPToolSpecificationFactoryTest extends
AbstractMCPToolSpecificationFactor
MCPResponse response = new MCPMapResponse(expectedPayload);
MCPToolDefinition toolDefinition =
mockSupportedTool(mockedToolDefinitionRegistry,
createPlanningToolDescriptor("database_gateway_plan_encrypt_rule"));
mockToolDispatch(mockedToolDefinitionRegistry, toolDefinition,
Map.of(), response);
- SyncToolSpecification actualSpecification = new
MCPToolSpecificationFactory(createRuntimeContext("stdio")).createToolSpecifications().getFirst();
McpSyncServerExchange exchange = createElicitationExchange(new
McpSchema.ElicitResult(action, Map.of()));
- CallToolResult actual =
actualSpecification.callHandler().apply(exchange, new
CallToolRequest("database_gateway_plan_encrypt_rule", Map.of()));
+ CallToolResult actual = callTool(createToolSpecification("stdio"),
exchange, "database_gateway_plan_encrypt_rule", Map.of());
assertThat(actual.structuredContent(), is(expectedPayload));
verify(exchange).createElicitation(any());
}
diff --git
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidator.java
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidator.java
index 0fee9873b34..b06bceaa202 100644
---
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidator.java
+++
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/descriptor/CoreToolDescriptorValidator.java
@@ -26,7 +26,6 @@ import
org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
/**
@@ -102,9 +101,9 @@ public final class CoreToolDescriptorValidator implements
MCPToolDescriptorValid
}
private void validateExecuteUpdateDescriptor(final MCPToolDescriptor
descriptor) {
- Map<?, ?> executionMode = findToolInputProperty(descriptor,
MCPPayloadFieldNames.EXECUTION_MODE).orElseThrow(
+ Map<?, ?> executionMode =
MCPToolDescriptorValidationUtils.findToolInputProperty(descriptor,
MCPPayloadFieldNames.EXECUTION_MODE).orElseThrow(
() -> new IllegalStateException("Tool
`database_gateway_execute_update` must declare execution_mode."));
- ShardingSpherePreconditions.checkState(isRequiredToolInput(descriptor,
MCPPayloadFieldNames.EXECUTION_MODE),
+
ShardingSpherePreconditions.checkState(MCPToolDescriptorValidationUtils.isRequiredToolInput(descriptor,
MCPPayloadFieldNames.EXECUTION_MODE),
() -> new IllegalStateException("Tool
`database_gateway_execute_update` execution_mode must be required."));
Object executionModes = executionMode.get("enum");
ShardingSpherePreconditions.checkState(executionModes instanceof
Collection && ((Collection<?>) executionModes).containsAll(List.of("execute",
"preview")),
@@ -112,17 +111,17 @@ public final class CoreToolDescriptorValidator implements
MCPToolDescriptorValid
}
private void validateRuntimeDatabaseContract(final MCPToolDescriptor
descriptor) {
- Map<?, ?> responseMode = findToolOutputProperty(descriptor,
"response_mode").orElseThrow(
+ Map<?, ?> responseMode =
MCPToolDescriptorValidationUtils.findToolOutputProperty(descriptor,
"response_mode").orElseThrow(
() -> new IllegalStateException("Tool
`database_gateway_validate_runtime_database` must declare response_mode."));
Object responseModes = responseMode.get("enum");
ShardingSpherePreconditions.checkState(responseModes instanceof
Collection && ((Collection<?>) responseModes).contains("validation"),
() -> new IllegalStateException("Tool
`database_gateway_validate_runtime_database` response_mode must allow
validation."));
-
ShardingSpherePreconditions.checkState(findToolInputProperty(descriptor,
"database").isPresent(),
+
ShardingSpherePreconditions.checkState(MCPToolDescriptorValidationUtils.findToolInputProperty(descriptor,
"database").isPresent(),
() -> new IllegalStateException("Tool
`database_gateway_validate_runtime_database` must declare `database`."));
- ShardingSpherePreconditions.checkState(isRequiredToolInput(descriptor,
"database"),
+
ShardingSpherePreconditions.checkState(MCPToolDescriptorValidationUtils.isRequiredToolInput(descriptor,
"database"),
() -> new IllegalStateException("Tool
`database_gateway_validate_runtime_database` database must be required."));
for (String each : List.of("databaseType", "jdbcUrl", "username",
"password", "driverClassName")) {
-
ShardingSpherePreconditions.checkState(findToolInputProperty(descriptor,
each).isEmpty(),
+
ShardingSpherePreconditions.checkState(MCPToolDescriptorValidationUtils.findToolInputProperty(descriptor,
each).isEmpty(),
() -> new IllegalStateException(String.format("Tool
`database_gateway_validate_runtime_database` must not expose `%s`.", each)));
}
}
@@ -144,26 +143,4 @@ public final class CoreToolDescriptorValidator implements
MCPToolDescriptorValid
}
}
- private Optional<Map<?, ?>> findToolInputProperty(final MCPToolDescriptor
descriptor, final String fieldName) {
- Object properties = descriptor.getInputSchema().get("properties");
- if (!(properties instanceof Map)) {
- return Optional.empty();
- }
- Object property = ((Map<?, ?>) properties).get(fieldName);
- return property instanceof Map ? Optional.of((Map<?, ?>) property) :
Optional.empty();
- }
-
- private Optional<Map<?, ?>> findToolOutputProperty(final MCPToolDescriptor
descriptor, final String fieldName) {
- Object properties = descriptor.getOutputSchema().get("properties");
- if (!(properties instanceof Map)) {
- return Optional.empty();
- }
- Object property = ((Map<?, ?>) properties).get(fieldName);
- return property instanceof Map ? Optional.of((Map<?, ?>) property) :
Optional.empty();
- }
-
- private boolean isRequiredToolInput(final MCPToolDescriptor descriptor,
final String fieldName) {
- Object required = descriptor.getInputSchema().get("required");
- return required instanceof Collection && ((Collection<?>)
required).contains(fieldName);
- }
}
diff --git
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionServiceTest.java
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionServiceTest.java
index cab4a8820f8..76261c5c109 100644
---
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionServiceTest.java
+++
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionServiceTest.java
@@ -65,11 +65,7 @@ class WorkflowExecutionServiceTest {
snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER
TABLE orders ADD COLUMN phone_mask VARCHAR(64)", 10));
snapshot.getIndexPlans().add(new IndexPlan("idx_orders_phone_mask",
"phone_mask", "mask lookup", "CREATE INDEX idx_orders_phone_mask ON
orders(phone_mask)"));
snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE
ENCRYPT RULE orders (PROPERTIES('aes-key-value'='123456'))"));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- mock(MCPFeatureExecutionFacade.class),
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(),
"manual-only");
+ Map<String, Object> actualResponse = apply(snapshot, List.of(),
"manual-only");
assertThat(actualResponse.get("status"),
is("awaiting-manual-execution"));
assertThat(actualResponse.get("response_mode"), is("manual_only"));
assertThat(actualResponse.get("plan_id"), is("plan-1"));
@@ -99,11 +95,7 @@ class WorkflowExecutionServiceTest {
snapshot.setWorkflowKind(WorkflowKind.valueOf("encrypt.rule"));
snapshot.getPropertyRequirements().add(new
AlgorithmPropertyRequirement("primary", "aes-key-value", true, true, "AES
key.", ""));
snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE
ENCRYPT RULE orders (PROPERTIES('aes-key-value'='123456'))"));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- mock(MCPFeatureExecutionFacade.class),
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(),
"manual-only");
+ Map<String, Object> actualResponse = apply(snapshot, List.of(),
"manual-only");
assertFalse(actualResponse.containsKey("executed_ddl"));
Map<?, ?> actualManualArtifactSummary = (Map<?, ?>)
actualResponse.get("manual_artifact_summary");
assertFalse(actualManualArtifactSummary.containsKey("ddl_artifact_count"));
@@ -133,13 +125,9 @@ class WorkflowExecutionServiceTest {
@Test
void assertApplyRejectsInvalidLifecycleStatus() {
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
WorkflowContextSnapshot snapshot = createSnapshot();
snapshot.setStatus("clarifying");
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- mock(MCPFeatureExecutionFacade.class),
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(),
"review-then-execute");
+ Map<String, Object> actualResponse = apply(snapshot, List.of(),
"review-then-execute");
assertThat(actualResponse.get("status"), is("failed"));
assertThat(actualResponse.get("response_mode"), is("terminal"));
assertThat(actualResponse.get("plan_id"), is("plan-1"));
@@ -179,13 +167,9 @@ class WorkflowExecutionServiceTest {
WorkflowContextSnapshot snapshot = createSnapshot();
snapshot.setStatus("previewed");
snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
MCPFeatureExecutionFacade executionFacade =
mock(MCPFeatureExecutionFacade.class);
when(executionFacade.execute(any())).thenReturn(mock(SQLExecutionResponse.class));
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP,
"session-1", snapshot, List.of(), "review-then-execute");
+ Map<String, Object> actualResponse = apply(snapshot, executionFacade,
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of(), "review-then-execute");
assertThat(actualResponse.get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>)
actualResponse.get("issues")).getFirst()).get("code"),
is(WorkflowIssueCode.WORKFLOW_STATUS_INVALID));
verify(executionFacade, never()).execute(any());
@@ -195,12 +179,8 @@ class WorkflowExecutionServiceTest {
void assertApplyRejectsExecutionBeforePreview() {
WorkflowContextSnapshot snapshot = createSnapshot();
snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
MCPFeatureExecutionFacade executionFacade =
mock(MCPFeatureExecutionFacade.class);
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP,
"session-1", snapshot, List.of("ddl"), "review-then-execute");
+ Map<String, Object> actualResponse = apply(snapshot, executionFacade,
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("ddl"),
"review-then-execute");
assertThat(actualResponse.get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>)
actualResponse.get("issues")).getFirst()).get("code"),
is(WorkflowIssueCode.WORKFLOW_STATUS_INVALID));
verify(executionFacade, never()).execute(any());
@@ -211,12 +191,8 @@ class WorkflowExecutionServiceTest {
WorkflowContextSnapshot snapshot = createSnapshot();
snapshot.setStatus("previewed");
snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE
MASK RULE orders"));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
MCPFeatureExecutionFacade executionFacade =
mock(MCPFeatureExecutionFacade.class);
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP,
"session-1", snapshot, List.of("ddl"), "review-then-execute");
+ Map<String, Object> actualResponse = apply(snapshot, executionFacade,
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("ddl"),
"review-then-execute");
assertThat(actualResponse.get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>)
actualResponse.get("issues")).getFirst()).get("code"),
is(WorkflowIssueCode.WORKFLOW_STATUS_INVALID));
verify(executionFacade, never()).execute(any());
@@ -227,13 +203,10 @@ class WorkflowExecutionServiceTest {
WorkflowContextSnapshot snapshot = createSnapshot();
snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE
MASK RULE orders"));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
+ WorkflowSessionContext workflowSessionContext =
createWorkflowSessionContext(snapshot);
MCPFeatureExecutionFacade executionFacade =
mock(MCPFeatureExecutionFacade.class);
MCPWorkflowApplySynchronizationHandler
workflowApplySynchronizationHandler =
mock(MCPWorkflowApplySynchronizationHandler.class);
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- executionFacade, workflowApplySynchronizationHandler,
"session-1", snapshot, List.of(), "preview");
+ Map<String, Object> actualResponse = apply(workflowSessionContext,
snapshot, executionFacade, workflowApplySynchronizationHandler, List.of(),
"preview");
assertThat(actualResponse.get("status"), is("preview"));
assertThat(actualResponse.get("response_mode"), is("preview"));
assertFalse((boolean) actualResponse.get("would_apply"));
@@ -265,11 +238,7 @@ class WorkflowExecutionServiceTest {
@Test
void assertApplyPreviewWithoutArtifacts() {
WorkflowContextSnapshot snapshot = createSnapshot();
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- mock(MCPFeatureExecutionFacade.class),
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(),
"preview");
+ Map<String, Object> actualResponse = apply(snapshot, List.of(),
"preview");
assertThat(actualResponse.get("review_summary"), is("Previewed 0
workflow artifacts. Nothing has been applied."));
assertFalse(actualResponse.containsKey("approval_question"));
assertThat(((Map<?, ?>) ((List<?>)
actualResponse.get("next_actions")).getFirst()).get("type"), is("terminal"));
@@ -282,11 +251,7 @@ class WorkflowExecutionServiceTest {
snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
snapshot.getIndexPlans().add(new
IndexPlan("idx_orders_order_id_cipher", "order_id_cipher", "assist lookup",
"CREATE INDEX idx_orders_order_id_cipher ON orders(order_id_cipher)"));
snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE
ENCRYPT RULE orders"));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- mock(MCPFeatureExecutionFacade.class),
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(),
"preview");
+ Map<String, Object> actualResponse = apply(snapshot, List.of(),
"preview");
assertFalse(actualResponse.containsKey("executed_ddl"));
assertThat(((List<?>) actualResponse.get("preview_artifacts")).size(),
is(1));
Map<?, ?> actualManualArtifactPackage = (Map<?, ?>)
actualResponse.get("manual_artifact_package");
@@ -303,11 +268,7 @@ class WorkflowExecutionServiceTest {
snapshot.setWorkflowKind(WorkflowKind.valueOf("encrypt.rule"));
snapshot.getPropertyRequirements().add(new
AlgorithmPropertyRequirement("primary", "aes-key-value", true, true, "AES
key.", ""));
snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE
ENCRYPT RULE orders (PROPERTIES('aes-key-value'='123456'))"));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- mock(MCPFeatureExecutionFacade.class),
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(),
"preview");
+ Map<String, Object> actualResponse = apply(snapshot, List.of(),
"preview");
Map<?, ?> actualPreviewArtifact = (Map<?, ?>) ((List<?>)
actualResponse.get("preview_artifacts")).getFirst();
assertThat(actualPreviewArtifact.get("sql"), is("CREATE ENCRYPT RULE
orders (PROPERTIES('aes-key-value'='******'))"));
assertFalse(String.valueOf(actualResponse).contains("123456"));
@@ -346,11 +307,7 @@ class WorkflowExecutionServiceTest {
WorkflowContextSnapshot snapshot = createSnapshot();
snapshot.getRequest().setExecutionMode("manual-only");
snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE
MASK RULE orders"));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- mock(MCPFeatureExecutionFacade.class),
MCPWorkflowApplySynchronizationHandler.NO_OP, "session-1", snapshot, List.of(),
"preview");
+ Map<String, Object> actualResponse = apply(snapshot, List.of(),
"preview");
List<?> actualNextActions = (List<?>)
actualResponse.get("next_actions");
assertThat(actualNextActions.size(), is(1));
Map<?, ?> actualNextAction = (Map<?, ?>) actualNextActions.getFirst();
@@ -364,13 +321,11 @@ class WorkflowExecutionServiceTest {
snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
snapshot.getIndexPlans().add(new
IndexPlan("idx_orders_order_id_cipher", "order_id_cipher", "assist lookup",
"CREATE INDEX idx_orders_order_id_cipher ON orders(order_id_cipher)"));
snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE
MASK RULE orders"));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
+ WorkflowSessionContext workflowSessionContext =
createWorkflowSessionContext(snapshot);
MCPFeatureExecutionFacade executionFacade =
mock(MCPFeatureExecutionFacade.class);
when(executionFacade.execute(any())).thenReturn(mock(SQLExecutionResponse.class));
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP,
"session-1", snapshot, List.of("ddl", "index_ddl", "rule_distsql"),
"review-then-execute");
+ Map<String, Object> actualResponse = apply(workflowSessionContext,
snapshot, executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP,
+ List.of("ddl", "index_ddl", "rule_distsql"),
"review-then-execute");
assertThat(actualResponse.get("status"), is("completed"));
assertThat(actualResponse.get("response_mode"), is("executed"));
assertThat(((List<?>) actualResponse.get("applied_artifacts")).size(),
is(3));
@@ -387,13 +342,9 @@ class WorkflowExecutionServiceTest {
snapshot.setWorkflowKind(WorkflowKind.valueOf("encrypt.rule"));
snapshot.getPropertyRequirements().add(new
AlgorithmPropertyRequirement("primary", "aes-key-value", true, true, "AES
key.", ""));
snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE
ENCRYPT RULE orders (PROPERTIES('aes-key-value'='123456'))"));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
MCPFeatureExecutionFacade executionFacade =
mock(MCPFeatureExecutionFacade.class);
when(executionFacade.execute(any())).thenReturn(mock(SQLExecutionResponse.class));
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP,
"session-1", snapshot, List.of("rule_distsql"), "review-then-execute");
+ Map<String, Object> actualResponse = apply(snapshot, executionFacade,
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("rule_distsql"),
"review-then-execute");
assertThat(actualResponse.get("status"), is("completed"));
assertThat(((List<?>)
actualResponse.get("executed_distsql")).getFirst(), is("CREATE ENCRYPT RULE
orders (PROPERTIES('aes-key-value'='******'))"));
assertThat(((Map<?, ?>) ((List<?>)
actualResponse.get("step_results")).getFirst()).get("sql"),
@@ -405,12 +356,8 @@ class WorkflowExecutionServiceTest {
@Test
void assertApplyRequiresManualExecutionForSecretReference() {
WorkflowContextSnapshot snapshot = createSecretReferenceSnapshot();
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
MCPFeatureExecutionFacade executionFacade =
mock(MCPFeatureExecutionFacade.class);
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP,
"session-1", snapshot, List.of("rule_distsql"), "review-then-execute");
+ Map<String, Object> actualResponse = apply(snapshot, executionFacade,
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("rule_distsql"),
"review-then-execute");
assertThat(actualResponse.get("status"), is("failed"));
assertThat(actualResponse.get("response_mode"), is("recovery"));
assertThat(actualResponse.get("category"),
is(MCPDiagnosticCategory.SECRET_REFERENCE_MANUAL_EXECUTION_REQUIRED));
@@ -429,14 +376,11 @@ class WorkflowExecutionServiceTest {
snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
snapshot.getIndexPlans().add(new
IndexPlan("idx_orders_order_id_cipher", "order_id_cipher", "assist lookup",
"CREATE INDEX idx_orders_order_id_cipher ON orders(order_id_cipher)"));
snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE
MASK RULE orders"));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
+ WorkflowSessionContext workflowSessionContext =
createWorkflowSessionContext(snapshot);
MCPFeatureExecutionFacade executionFacade =
mock(MCPFeatureExecutionFacade.class);
when(executionFacade.execute(any())).thenReturn(mock(SQLExecutionResponse.class));
MCPWorkflowApplySynchronizationHandler
workflowApplySynchronizationHandler =
mock(MCPWorkflowApplySynchronizationHandler.class);
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- executionFacade, workflowApplySynchronizationHandler,
"session-1", snapshot, List.of("ddl"), "review-then-execute");
+ Map<String, Object> actualResponse = apply(workflowSessionContext,
snapshot, executionFacade, workflowApplySynchronizationHandler, List.of("ddl"),
"review-then-execute");
List<?> actualStepResults = (List<?>)
actualResponse.get("step_results");
assertThat(actualResponse.get("status"), is("completed"));
assertThat(((List<?>) actualResponse.get("skipped_artifacts")).size(),
is(2));
@@ -449,19 +393,17 @@ class WorkflowExecutionServiceTest {
@Test
void assertApplyFailsWhenSynchronizationDoesNotConverge() {
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
WorkflowContextSnapshot snapshot = createSnapshot();
snapshot.setStatus("previewed");
snapshot.getRuleArtifacts().add(new RuleArtifact("create", "ALTER MASK
RULE orders"));
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
+ WorkflowSessionContext workflowSessionContext =
createWorkflowSessionContext(snapshot);
MCPFeatureExecutionFacade executionFacade =
mock(MCPFeatureExecutionFacade.class);
when(executionFacade.execute(any())).thenReturn(mock(SQLExecutionResponse.class));
MCPWorkflowApplySynchronizationHandler
workflowApplySynchronizationHandler =
mock(MCPWorkflowApplySynchronizationHandler.class);
doThrow(new
WorkflowSynchronizationException(WorkflowIssueCode.RULE_STATE_MISMATCH, "Mask
rule is missing.",
List.of(Map.of("code",
WorkflowIssueCode.RULE_STATE_MISMATCH)))).when(workflowApplySynchronizationHandler).synchronize(any(),
any(), any(), any(), any());
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- executionFacade, workflowApplySynchronizationHandler,
"session-1", snapshot, List.of("rule_distsql"), "review-then-execute");
+ Map<String, Object> actualResponse = apply(workflowSessionContext,
snapshot, executionFacade, workflowApplySynchronizationHandler,
+ List.of("rule_distsql"), "review-then-execute");
Map<?, ?> actualIssue = (Map<?, ?>) ((List<?>)
actualResponse.get("issues")).getFirst();
assertThat(actualResponse.get("status"), is("failed"));
assertThat(actualIssue.get("code"),
is(WorkflowIssueCode.RULE_STATE_MISMATCH));
@@ -470,16 +412,12 @@ class WorkflowExecutionServiceTest {
@Test
void assertApplyReturnsDdlExecutionFailureForDdlArtifact() {
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
WorkflowContextSnapshot snapshot = createSnapshot();
snapshot.setStatus("previewed");
snapshot.getDdlArtifacts().add(new DDLArtifact("add-column", "ALTER
TABLE orders ADD COLUMN order_id_cipher VARCHAR(32)", 10));
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
MCPFeatureExecutionFacade executionFacade =
mock(MCPFeatureExecutionFacade.class);
when(executionFacade.execute(any())).thenThrow(new
IllegalStateException("ddl failed"));
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP,
"session-1", snapshot, List.of("ddl"), "review-then-execute");
+ Map<String, Object> actualResponse = apply(snapshot, executionFacade,
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("ddl"),
"review-then-execute");
Map<?, ?> actualIssue = (Map<?, ?>) ((List<?>)
actualResponse.get("issues")).getFirst();
Map<?, ?> actualStep = (Map<?, ?>) ((List<?>)
actualResponse.get("step_results")).getFirst();
assertThat(actualResponse.get("status"), is("failed"));
@@ -489,16 +427,12 @@ class WorkflowExecutionServiceTest {
@Test
void assertApplyReturnsDdlExecutionFailureForIndexArtifact() {
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
WorkflowContextSnapshot snapshot = createSnapshot();
snapshot.setStatus("previewed");
snapshot.getIndexPlans().add(new
IndexPlan("idx_orders_order_id_cipher", "order_id_cipher", "assist lookup",
"CREATE INDEX idx_orders_order_id_cipher ON orders(order_id_cipher)"));
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
MCPFeatureExecutionFacade executionFacade =
mock(MCPFeatureExecutionFacade.class);
when(executionFacade.execute(any())).thenThrow(new
IllegalStateException("index failed"));
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP,
"session-1", snapshot, List.of("index_ddl"), "review-then-execute");
+ Map<String, Object> actualResponse = apply(snapshot, executionFacade,
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("index_ddl"),
"review-then-execute");
Map<?, ?> actualIssue = (Map<?, ?>) ((List<?>)
actualResponse.get("issues")).getFirst();
Map<?, ?> actualStep = (Map<?, ?>) ((List<?>)
actualResponse.get("step_results")).getFirst();
assertThat(actualResponse.get("status"), is("failed"));
@@ -513,13 +447,9 @@ class WorkflowExecutionServiceTest {
snapshot.setWorkflowKind(WorkflowKind.valueOf("encrypt.rule"));
snapshot.getPropertyRequirements().add(new
AlgorithmPropertyRequirement("primary", "aes-key-value", true, true, "AES
key.", ""));
snapshot.getRuleArtifacts().add(new RuleArtifact("create", "CREATE
ENCRYPT RULE orders (PROPERTIES('aes-key-value'='123456'))"));
- WorkflowSessionContext workflowSessionContext = new
InMemoryWorkflowSessionContext();
- workflowSessionContext.save(snapshot);
- WorkflowExecutionService executionService = new
WorkflowExecutionService();
MCPFeatureExecutionFacade executionFacade =
mock(MCPFeatureExecutionFacade.class);
when(executionFacade.execute(any())).thenThrow(new
IllegalStateException("Failed to execute CREATE ENCRYPT RULE orders
(PROPERTIES('aes-key-value'='123456'))"));
- Map<String, Object> actualResponse =
executionService.apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
- executionFacade, MCPWorkflowApplySynchronizationHandler.NO_OP,
"session-1", snapshot, List.of("rule_distsql"), "review-then-execute");
+ Map<String, Object> actualResponse = apply(snapshot, executionFacade,
MCPWorkflowApplySynchronizationHandler.NO_OP, List.of("rule_distsql"),
"review-then-execute");
Map<?, ?> actualIssue = (Map<?, ?>) ((List<?>)
actualResponse.get("issues")).getFirst();
assertThat(actualResponse.get("status"), is("failed"));
assertThat(actualIssue.get("code"),
is(WorkflowIssueCode.RULE_EXECUTION_FAILED));
@@ -527,6 +457,30 @@ class WorkflowExecutionServiceTest {
assertFalse(String.valueOf(actualResponse).contains("123456"));
}
+ private Map<String, Object> apply(final WorkflowContextSnapshot snapshot,
final List<String> approvedSteps, final String executionMode) {
+ return apply(createWorkflowSessionContext(snapshot), snapshot,
mock(MCPFeatureExecutionFacade.class),
MCPWorkflowApplySynchronizationHandler.NO_OP, approvedSteps, executionMode);
+ }
+
+ private Map<String, Object> apply(final WorkflowContextSnapshot snapshot,
final MCPFeatureExecutionFacade executionFacade,
+ final
MCPWorkflowApplySynchronizationHandler workflowApplySynchronizationHandler,
final List<String> approvedSteps,
+ final String executionMode) {
+ return apply(createWorkflowSessionContext(snapshot), snapshot,
executionFacade, workflowApplySynchronizationHandler, approvedSteps,
executionMode);
+ }
+
+ private Map<String, Object> apply(final WorkflowSessionContext
workflowSessionContext, final WorkflowContextSnapshot snapshot,
+ final MCPFeatureExecutionFacade
executionFacade,
+ final
MCPWorkflowApplySynchronizationHandler workflowApplySynchronizationHandler,
final List<String> approvedSteps,
+ final String executionMode) {
+ return new WorkflowExecutionService().apply(workflowSessionContext,
mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
+ executionFacade, workflowApplySynchronizationHandler,
"session-1", snapshot, approvedSteps, executionMode);
+ }
+
+ private WorkflowSessionContext createWorkflowSessionContext(final
WorkflowContextSnapshot snapshot) {
+ WorkflowSessionContext result = new InMemoryWorkflowSessionContext();
+ result.save(snapshot);
+ return result;
+ }
+
private WorkflowContextSnapshot createSnapshot() {
WorkflowContextSnapshot result = new WorkflowContextSnapshot();
result.setPlanId("plan-1");
diff --git
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidator.java
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidator.java
index 61e3da032a5..9dc9a6a8361 100644
---
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidator.java
+++
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidator.java
@@ -577,9 +577,9 @@ final class MCPDescriptorCatalogValidator {
if (!descriptor.getAnnotations().isDestructiveHint()) {
return;
}
- Map<?, ?> executionMode = findToolInputProperty(descriptor,
MCPPayloadFieldNames.EXECUTION_MODE).orElseThrow(
+ Map<?, ?> executionMode =
MCPToolDescriptorValidationUtils.findToolInputProperty(descriptor,
MCPPayloadFieldNames.EXECUTION_MODE).orElseThrow(
() -> new IllegalStateException(String.format("Destructive
tool `%s` must declare execution_mode.", descriptor.getName())));
- ShardingSpherePreconditions.checkState(isRequiredToolInput(descriptor,
MCPPayloadFieldNames.EXECUTION_MODE),
+
ShardingSpherePreconditions.checkState(MCPToolDescriptorValidationUtils.isRequiredToolInput(descriptor,
MCPPayloadFieldNames.EXECUTION_MODE),
() -> new IllegalStateException(String.format("Destructive
tool `%s` execution_mode must be required.", descriptor.getName())));
Collection<?> executionModes = executionMode.get("enum") instanceof
Collection ? (Collection<?>) executionMode.get("enum") : List.of();
ShardingSpherePreconditions.checkState(executionModes.contains("preview"),
@@ -594,7 +594,7 @@ final class MCPDescriptorCatalogValidator {
if (descriptor.getAnnotations().isDestructiveHint()) {
return;
}
- Optional<Map<?, ?>> executionMode = findToolInputProperty(descriptor,
MCPPayloadFieldNames.EXECUTION_MODE);
+ Optional<Map<?, ?>> executionMode =
MCPToolDescriptorValidationUtils.findToolInputProperty(descriptor,
MCPPayloadFieldNames.EXECUTION_MODE);
if (executionMode.isEmpty()) {
return;
}
@@ -605,20 +605,6 @@ final class MCPDescriptorCatalogValidator {
() -> new IllegalStateException(String.format("Planning tool
`%s` execution_mode must not expose auto-execute.", descriptor.getName())));
}
- private static Optional<Map<?, ?>> findToolInputProperty(final
MCPToolDescriptor descriptor, final String fieldName) {
- Object properties = descriptor.getInputSchema().get("properties");
- if (!(properties instanceof Map)) {
- return Optional.empty();
- }
- Object property = ((Map<?, ?>) properties).get(fieldName);
- return property instanceof Map ? Optional.of((Map<?, ?>) property) :
Optional.empty();
- }
-
- private static boolean isRequiredToolInput(final MCPToolDescriptor
descriptor, final String fieldName) {
- Object required = descriptor.getInputSchema().get("required");
- return required instanceof Collection && ((Collection<?>)
required).contains(fieldName);
- }
-
private static boolean isNonEmptyCollection(final Object value) {
return value instanceof Collection && !((Collection<?>)
value).isEmpty();
}
diff --git
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtils.java
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtils.java
index f005de4719f..1a6c5f3b49f 100644
---
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtils.java
+++
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtils.java
@@ -26,6 +26,7 @@ import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
/**
* MCP tool descriptor validation utilities.
@@ -85,6 +86,49 @@ public final class MCPToolDescriptorValidationUtils {
}
}
+ /**
+ * Find input schema property.
+ *
+ * @param descriptor tool descriptor
+ * @param fieldName field name
+ * @return found input schema property
+ */
+ public static Optional<Map<?, ?>> findToolInputProperty(final
MCPToolDescriptor descriptor, final String fieldName) {
+ return findToolSchemaProperty(descriptor.getInputSchema(), fieldName);
+ }
+
+ /**
+ * Find output schema property.
+ *
+ * @param descriptor tool descriptor
+ * @param fieldName field name
+ * @return found output schema property
+ */
+ public static Optional<Map<?, ?>> findToolOutputProperty(final
MCPToolDescriptor descriptor, final String fieldName) {
+ return findToolSchemaProperty(descriptor.getOutputSchema(), fieldName);
+ }
+
+ /**
+ * Check whether input schema property is required.
+ *
+ * @param descriptor tool descriptor
+ * @param fieldName field name
+ * @return whether input schema property is required
+ */
+ public static boolean isRequiredToolInput(final MCPToolDescriptor
descriptor, final String fieldName) {
+ Object required = descriptor.getInputSchema().get("required");
+ return required instanceof Collection && ((Collection<?>)
required).contains(fieldName);
+ }
+
+ private static Optional<Map<?, ?>> findToolSchemaProperty(final
Map<String, Object> schema, final String fieldName) {
+ Object properties = schema.get("properties");
+ if (!(properties instanceof Map)) {
+ return Optional.empty();
+ }
+ Object property = ((Map<?, ?>) properties).get(fieldName);
+ return property instanceof Map ? Optional.of((Map<?, ?>) property) :
Optional.empty();
+ }
+
/**
* Validate required workflow planning metadata fields.
*
diff --git
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcMetadataLoaderTest.java
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcMetadataLoaderTest.java
index c1174c11fd5..86302879859 100644
---
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcMetadataLoaderTest.java
+++
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/metadata/jdbc/MCPJdbcMetadataLoaderTest.java
@@ -70,6 +70,15 @@ import static org.mockito.Mockito.when;
class MCPJdbcMetadataLoaderTest {
+ private static final Map<String, String> METADATA_JDBC_URLS = Map.of(
+ "MySQL", "jdbc:mysql://metadata-loader/test",
+ "PostgreSQL", "jdbc:postgresql://metadata-loader/test",
+ "openGauss", "jdbc:opengauss://metadata-loader/test",
+ "SQLServer", "jdbc:sqlserver://metadata-loader",
+ "Oracle", "jdbc:oracle:thin:@metadata-loader",
+ "MariaDB", "jdbc:mariadb://metadata-loader/test",
+ "Firebird", "jdbc:firebirdsql://metadata-loader/test");
+
@Test
void assertLoad() throws SQLException {
LoadedMetadataCatalog actual = load(Map.of("logic_db",
createMockRuntimeDatabaseConfiguration(createStandardPostgreSQLMetadataConnection())));
@@ -97,8 +106,7 @@ class MCPJdbcMetadataLoaderTest {
@Test
void assertLoadWithoutSchemaObjects() throws SQLException {
Driver mockDriver = new MockDriver("jdbc:mock:no-schema",
createConnectionWithoutSchema("MySQL"));
- DriverManager.registerDriver(mockDriver);
- try {
+ try (MockDriverRegistration registration =
MockDriverRegistration.register(mockDriver)) {
LoadedMetadataCatalog actual = load(Map.of("logic_db", new
RuntimeDatabaseConfiguration("jdbc:mock:no-schema", "", "",
MockDriver.class.getName())));
MCPDatabaseMetadata databaseMetadata =
actual.findMetadata("logic_db").orElseThrow();
assertThat(databaseMetadata.getSchemas().size(), is(1));
@@ -106,8 +114,6 @@ class MCPJdbcMetadataLoaderTest {
assertTrue(containsMetadata(databaseMetadata,
SupportedMCPMetadataObjectType.TABLE, "orders"));
assertTrue(containsMetadata(databaseMetadata,
SupportedMCPMetadataObjectType.COLUMN, "order_id"));
assertThat(databaseMetadata.getDatabaseVersion(), is(""));
- } finally {
- DriverManager.deregisterDriver(mockDriver);
}
}
@@ -306,14 +312,11 @@ class MCPJdbcMetadataLoaderTest {
@Test
void assertLoadWithFailedSequenceMetadataQuery() throws SQLException {
Driver mockDriver = new MockDriver("jdbc:mock:failed-sequence-query",
createConnectionWithFailedSequenceMetadataQuery());
- DriverManager.registerDriver(mockDriver);
- try {
+ try (MockDriverRegistration registration =
MockDriverRegistration.register(mockDriver)) {
RuntimeDatabaseConnectionException actual =
assertThrows(RuntimeDatabaseConnectionException.class,
() -> load(Map.of("logic_db", new
RuntimeDatabaseConfiguration("jdbc:mock:failed-sequence-query", "", "",
MockDriver.class.getName()))));
assertThat(actual.getMessage(), is("Runtime database `logic_db`
connection failed: connection_failed."));
assertThat(actual.getCause().getMessage(), is("sequence metadata
query failed"));
- } finally {
- DriverManager.deregisterDriver(mockDriver);
}
}
@@ -323,12 +326,9 @@ class MCPJdbcMetadataLoaderTest {
final String sequenceName,
final String sequenceQuery) throws SQLException {
String jdbcUrl = "jdbc:mock:sequence:" +
databaseType.toLowerCase(Locale.ENGLISH);
Driver mockDriver = new MockDriver(jdbcUrl,
createConnectionWithSequenceMetadata(databaseType, sequenceSchema,
sequenceName, sequenceQuery));
- DriverManager.registerDriver(mockDriver);
- try {
+ try (MockDriverRegistration registration =
MockDriverRegistration.register(mockDriver)) {
LoadedMetadataCatalog actual = load(Map.of("logic_db", new
RuntimeDatabaseConfiguration(jdbcUrl, "", "", MockDriver.class.getName())));
assertTrue(containsMetadata(actual.findMetadata("logic_db").orElseThrow(),
SupportedMCPMetadataObjectType.SEQUENCE, sequenceName));
- } finally {
- DriverManager.deregisterDriver(mockDriver);
}
}
@@ -558,28 +558,7 @@ class MCPJdbcMetadataLoaderTest {
}
private String getMetadataJdbcUrl(final String databaseType) {
- if ("MySQL".equals(databaseType)) {
- return "jdbc:mysql://metadata-loader/test";
- }
- if ("PostgreSQL".equals(databaseType)) {
- return "jdbc:postgresql://metadata-loader/test";
- }
- if ("openGauss".equals(databaseType)) {
- return "jdbc:opengauss://metadata-loader/test";
- }
- if ("SQLServer".equals(databaseType)) {
- return "jdbc:sqlserver://metadata-loader";
- }
- if ("Oracle".equals(databaseType)) {
- return "jdbc:oracle:thin:@metadata-loader";
- }
- if ("MariaDB".equals(databaseType)) {
- return "jdbc:mariadb://metadata-loader/test";
- }
- if ("Firebird".equals(databaseType)) {
- return "jdbc:firebirdsql://metadata-loader/test";
- }
- return "jdbc:postgresql://metadata-loader/test";
+ return METADATA_JDBC_URLS.getOrDefault(databaseType,
METADATA_JDBC_URLS.get("PostgreSQL"));
}
private int countMetadata(final MCPDatabaseMetadata databaseMetadata,
final SupportedMCPMetadataObjectType objectType, final String objectName) {
@@ -640,6 +619,25 @@ class MCPJdbcMetadataLoaderTest {
}
}
+ private static final class MockDriverRegistration implements AutoCloseable
{
+
+ private final Driver driver;
+
+ private MockDriverRegistration(final Driver driver) throws
SQLException {
+ this.driver = driver;
+ DriverManager.registerDriver(driver);
+ }
+
+ private static MockDriverRegistration register(final Driver driver)
throws SQLException {
+ return new MockDriverRegistration(driver);
+ }
+
+ @Override
+ public void close() throws SQLException {
+ DriverManager.deregisterDriver(driver);
+ }
+ }
+
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
private static final class MockDriver implements Driver {
diff --git
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtilsTest.java
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtilsTest.java
index 7cc972d503b..1af04338ea6 100644
---
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtilsTest.java
+++
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPToolDescriptorValidationUtilsTest.java
@@ -27,11 +27,13 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
class MCPToolDescriptorValidationUtilsTest {
@@ -68,6 +70,32 @@ class MCPToolDescriptorValidationUtilsTest {
assertThat(actual.getMessage(), is("Tool `fixture_tool` outputSchema
property `status` must be an object."));
}
+ @Test
+ void assertFindToolInputProperty() {
+ MCPToolDescriptor descriptor = createDescriptor(
+ Map.of("properties", Map.of("database", Map.of("type",
"string"))), Map.of());
+ Optional<Map<?, ?>> actual =
MCPToolDescriptorValidationUtils.findToolInputProperty(descriptor, "database");
+ assertTrue(actual.isPresent());
+ assertThat(actual.get().get("type"), is("string"));
+ }
+
+ @Test
+ void assertFindToolOutputProperty() {
+ MCPToolDescriptor descriptor = createDescriptor(
+ Map.of(), Map.of("properties", Map.of("response_mode",
Map.of("enum", List.of("validation")))));
+ Optional<Map<?, ?>> actual =
MCPToolDescriptorValidationUtils.findToolOutputProperty(descriptor,
"response_mode");
+ assertTrue(actual.isPresent());
+ assertThat(actual.get().get("enum"), is(List.of("validation")));
+ }
+
+ @Test
+ void assertIsRequiredToolInput() {
+ MCPToolDescriptor descriptor = createDescriptor(
+ Map.of("required", List.of("database"), "properties",
Map.of("database", Map.of("type", "string"))), Map.of());
+ boolean actual =
MCPToolDescriptorValidationUtils.isRequiredToolInput(descriptor, "database");
+ assertTrue(actual);
+ }
+
@Test
void assertValidateRequiredWorkflowPlanMetaFields() {
MCPToolDescriptor descriptor =
createDescriptor(WORKFLOW_PLAN_OUTPUT_FIELDS, createWorkflowPlanMeta());
@@ -93,6 +121,10 @@ class MCPToolDescriptorValidationUtilsTest {
Map.of("type", "object", "properties",
createOutputProperties(outputFields)), createAnnotations(), meta);
}
+ private MCPToolDescriptor createDescriptor(final Map<String, Object>
inputSchema, final Map<String, Object> outputSchema) {
+ return new MCPToolDescriptor("fixture_tool", "Fixture Tool", "Fixture
tool.", inputSchema, outputSchema, createAnnotations(), Map.of());
+ }
+
private MCPToolAnnotations createAnnotations() {
return new MCPToolAnnotations("Fixture Tool", true, false, true,
false);
}