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 8c1f9e0ffa9 Unify runtime protection and diagnostics metadata (#38748)
8c1f9e0ffa9 is described below
commit 8c1f9e0ffa9069cbb571e5aca5b887dac9923b26
Author: Liang Zhang <[email protected]>
AuthorDate: Fri May 29 09:25:50 2026 +0800
Unify runtime protection and diagnostics metadata (#38748)
---
.../handler/capability/RuntimeStatusHandler.java | 4 +-
.../execute/SQLExecutionToolHandlerSupport.java | 22 ++---
.../capability/RuntimeStatusHandlerTest.java | 21 +++++
.../capability/ServerCapabilitiesHandlerTest.java | 1 +
.../support/security/MCPClientSafetyPolicy.java | 22 ++---
...Policy.java => MCPRuntimeProtectionPolicy.java} | 76 ++++++++++++-----
.../MCPModelFirstContractPayloadBuilderTest.java | 1 +
.../security/MCPClientSafetyPolicyTest.java | 31 +++++--
.../security/MCPRuntimeProtectionPolicyTest.java | 96 ++++++++++++++++++++++
9 files changed, 217 insertions(+), 57 deletions(-)
diff --git
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/RuntimeStatusHandler.java
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/RuntimeStatusHandler.java
index 758d116fb12..8e892f84ad3 100644
---
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/RuntimeStatusHandler.java
+++
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/RuntimeStatusHandler.java
@@ -31,6 +31,7 @@ import
org.apache.shardingsphere.mcp.support.protocol.MCPResourceHintUtils;
import org.apache.shardingsphere.mcp.support.protocol.MCPResponseMode;
import org.apache.shardingsphere.mcp.support.protocol.response.MCPMapResponse;
import org.apache.shardingsphere.mcp.support.resource.MCPUriPathSegmentUtils;
+import
org.apache.shardingsphere.mcp.support.security.MCPRuntimeProtectionPolicy;
import java.util.LinkedHashMap;
import java.util.List;
@@ -58,7 +59,7 @@ public final class RuntimeStatusHandler implements
MCPResourceHandler<MCPDatabas
public MCPResponse handle(final MCPDatabaseHandlerContext handlerContext,
final MCPUriVariables uriVariables) {
List<MCPDatabaseMetadata> databases =
handlerContext.getMetadataQueryFacade().queryDatabases();
boolean hasConfiguredDatabase = !databases.isEmpty();
- Map<String, Object> result = new LinkedHashMap<>(13, 1F);
+ Map<String, Object> result = new LinkedHashMap<>(14, 1F);
result.put("response_mode", MCPResponseMode.RUNTIME);
result.put("server_status", hasConfiguredDatabase ? "ready" :
"configuration_required");
result.put("status", hasConfiguredDatabase ? "available" :
"configuration_required");
@@ -67,6 +68,7 @@ public final class RuntimeStatusHandler implements
MCPResourceHandler<MCPDatabas
result.put("configured_database_count", databases.size());
result.put("databases", databases.stream().map(each ->
createDatabaseStatus(handlerContext, each)).toList());
result.put("readiness", createReadiness(hasConfiguredDatabase));
+ result.put("runtime_protection",
MCPRuntimeProtectionPolicy.createRuntimeProtectionPayload());
result.put("redaction_summary", Map.of("categories", List.of(),
"redacted_count", 0, "marker", "******"));
result.put("diagnostics", createDiagnostics(hasConfiguredDatabase));
result.put("capability_fingerprint",
MCPDescriptorCatalogIndex.getDescriptorCatalogFingerprint());
diff --git
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLExecutionToolHandlerSupport.java
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLExecutionToolHandlerSupport.java
index 095a21cd13b..97ba784e7ce 100644
---
a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLExecutionToolHandlerSupport.java
+++
b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLExecutionToolHandlerSupport.java
@@ -25,20 +25,13 @@ import
org.apache.shardingsphere.mcp.core.protocol.exception.MCPInvalidToolArgum
import org.apache.shardingsphere.mcp.core.tool.request.MCPToolArguments;
import
org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
import
org.apache.shardingsphere.mcp.support.database.tool.request.SQLExecutionRequest;
+import
org.apache.shardingsphere.mcp.support.security.MCPRuntimeProtectionPolicy;
import java.util.Map;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
final class SQLExecutionToolHandlerSupport {
- private static final int DEFAULT_MAX_ROWS = 100;
-
- private static final int MAX_ROWS_LIMIT = 5000;
-
- private static final int DEFAULT_TIMEOUT_MILLISECONDS = 0;
-
- private static final int MAX_TIMEOUT_MILLISECONDS = 300000;
-
static boolean isReadOnlyStatement(final ClassificationResult
classificationResult) {
if (SupportedMCPStatement.QUERY ==
classificationResult.getStatementClass()) {
return true;
@@ -49,7 +42,8 @@ final class SQLExecutionToolHandlerSupport {
static void checkExecutionArguments(final MCPToolArguments toolArguments,
final String sourceTool) {
resolveMaxRows(toolArguments, sourceTool);
- getIntegerArgument(toolArguments, sourceTool, "timeout_ms",
DEFAULT_TIMEOUT_MILLISECONDS, DEFAULT_TIMEOUT_MILLISECONDS,
MAX_TIMEOUT_MILLISECONDS, DEFAULT_TIMEOUT_MILLISECONDS);
+ getIntegerArgument(toolArguments, sourceTool, "timeout_ms",
MCPRuntimeProtectionPolicy.DEFAULT_TIMEOUT_MILLISECONDS,
MCPRuntimeProtectionPolicy.DEFAULT_TIMEOUT_MILLISECONDS,
+ MCPRuntimeProtectionPolicy.MAX_TIMEOUT_MILLISECONDS,
MCPRuntimeProtectionPolicy.DEFAULT_TIMEOUT_MILLISECONDS);
}
static SQLExecutionRequest createExecutionRequest(final MCPToolCall
toolCall, final MCPToolArguments toolArguments, final String sql, final String
sourceTool) {
@@ -63,8 +57,9 @@ final class SQLExecutionToolHandlerSupport {
private static SQLExecutionRequest createExecutionRequest(final
MCPToolCall toolCall, final MCPToolArguments toolArguments, final String
schema, final String sql, final String sourceTool,
final boolean
readOnlyExecution) {
return new SQLExecutionRequest(toolCall.getSessionId(),
toolArguments.getStringArgument("database"), schema, sql,
- resolveMaxRows(toolArguments, sourceTool),
getIntegerArgument(toolArguments, sourceTool, "timeout_ms",
DEFAULT_TIMEOUT_MILLISECONDS, DEFAULT_TIMEOUT_MILLISECONDS,
- MAX_TIMEOUT_MILLISECONDS,
DEFAULT_TIMEOUT_MILLISECONDS),
+ resolveMaxRows(toolArguments, sourceTool),
getIntegerArgument(toolArguments, sourceTool, "timeout_ms",
MCPRuntimeProtectionPolicy.DEFAULT_TIMEOUT_MILLISECONDS,
+
MCPRuntimeProtectionPolicy.DEFAULT_TIMEOUT_MILLISECONDS,
MCPRuntimeProtectionPolicy.MAX_TIMEOUT_MILLISECONDS,
+
MCPRuntimeProtectionPolicy.DEFAULT_TIMEOUT_MILLISECONDS),
readOnlyExecution);
}
@@ -73,8 +68,9 @@ final class SQLExecutionToolHandlerSupport {
}
private static int resolveMaxRows(final MCPToolArguments toolArguments,
final String sourceTool) {
- int result = getIntegerArgument(toolArguments, sourceTool, "max_rows",
DEFAULT_MAX_ROWS, 0, MAX_ROWS_LIMIT, DEFAULT_MAX_ROWS);
- return 0 == result ? DEFAULT_MAX_ROWS : result;
+ int result = getIntegerArgument(toolArguments, sourceTool, "max_rows",
MCPRuntimeProtectionPolicy.DEFAULT_MAX_ROWS, 0,
MCPRuntimeProtectionPolicy.MAX_ROWS_LIMIT,
+ MCPRuntimeProtectionPolicy.DEFAULT_MAX_ROWS);
+ return 0 == result ? MCPRuntimeProtectionPolicy.DEFAULT_MAX_ROWS :
result;
}
private static int getIntegerArgument(final MCPToolArguments
toolArguments, final String sourceTool, final String argumentPath, final int
defaultValue, final int minimumValue,
diff --git
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/RuntimeStatusHandlerTest.java
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/RuntimeStatusHandlerTest.java
index 12a54da1e7c..38d66935792 100644
---
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/RuntimeStatusHandlerTest.java
+++
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/RuntimeStatusHandlerTest.java
@@ -20,6 +20,7 @@ package
org.apache.shardingsphere.mcp.core.resource.handler.capability;
import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
import org.apache.shardingsphere.mcp.core.context.MCPRequestScope;
import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory;
+import
org.apache.shardingsphere.mcp.support.security.MCPRuntimeProtectionPolicy;
import org.junit.jupiter.api.Test;
import java.util.List;
@@ -45,6 +46,7 @@ class RuntimeStatusHandlerTest {
assertTrue(((List<?>) actual.get("databases")).stream().map(each
-> ((Map<?, ?>) each).get("database")).anyMatch("logic_db"::equals));
assertThat(((Map<?, ?>)
actual.get("redaction_summary")).get("marker"), is("******"));
assertRuntimeDiagnostics(actual, "ready");
+ assertRuntimeProtection(actual);
assertTrue(String.valueOf(actual.get("capability_fingerprint")).matches("[0-9a-f]{64}"));
assertRuntimeCapability((List<?>) actual.get("databases"),
"logic_db");
assertThat(extractResourceUris((List<?>)
actual.get("resources_to_read")), is(List.of("shardingsphere://capabilities",
"shardingsphere://databases")));
@@ -73,6 +75,7 @@ class RuntimeStatusHandlerTest {
assertFalse((Boolean) readiness.get("ready"));
assertThat(readiness.get("reason"), is("No runtime databases are
configured."));
assertRuntimeDiagnostics(actual, "invalid_configuration");
+ assertRuntimeProtection(actual);
assertThat(extractResourceUris((List<?>)
actual.get("resources_to_read")), is(List.of("shardingsphere://capabilities")));
List<?> nextActions = (List<?>) actual.get("next_actions");
assertThat(((Map<?, ?>) nextActions.get(0)).get("type"),
is("resource_read"));
@@ -97,6 +100,24 @@ class RuntimeStatusHandlerTest {
assertTrue((Boolean) ((Map<?, ?>)
actualOperatorNextActions.get(3)).get("secret_safe"));
}
+ private void assertRuntimeProtection(final Map<String, Object> payload) {
+ Map<?, ?> actualRuntimeProtection = (Map<?, ?>)
payload.get("runtime_protection");
+ Map<?, ?> actualToolCallLimit = (Map<?, ?>)
actualRuntimeProtection.get("tool_call_limit");
+ assertThat(actualToolCallLimit.get("scope"), is("session"));
+ assertThat(actualToolCallLimit.get("property"),
is(MCPRuntimeProtectionPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY));
+ Map<?, ?> actualSQLExecutionLimits = (Map<?, ?>)
actualRuntimeProtection.get("sql_execution_limits");
+ Map<?, ?> actualMaxRows = (Map<?, ?>)
actualSQLExecutionLimits.get("max_rows");
+ assertThat(actualMaxRows.get("default_value"),
is(MCPRuntimeProtectionPolicy.DEFAULT_MAX_ROWS));
+ assertThat(actualMaxRows.get("maximum_value"),
is(MCPRuntimeProtectionPolicy.MAX_ROWS_LIMIT));
+ assertThat(actualMaxRows.get("applied_field"), is("applied_max_rows"));
+ assertThat(actualMaxRows.get("truncation_field"), is("truncated"));
+ Map<?, ?> actualTimeout = (Map<?, ?>)
actualSQLExecutionLimits.get("timeout_ms");
+ assertThat(actualTimeout.get("default_value"),
is(MCPRuntimeProtectionPolicy.DEFAULT_TIMEOUT_MILLISECONDS));
+ assertThat(actualTimeout.get("maximum_value"),
is(MCPRuntimeProtectionPolicy.MAX_TIMEOUT_MILLISECONDS));
+ assertThat(actualTimeout.get("applied_field"),
is("applied_timeout_ms"));
+ assertThat(actualTimeout.get("zero_means"), is("server_default"));
+ }
+
private void assertRuntimeCapability(final List<?> databases, final String
databaseName) {
Map<?, ?> actualDatabase = databases.stream().map(each -> (Map<?, ?>)
each).filter(each ->
databaseName.equals(each.get("database"))).findFirst().orElseThrow();
assertThat(actualDatabase.get("metadata_visibility"), is("ready"));
diff --git
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/ServerCapabilitiesHandlerTest.java
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/ServerCapabilitiesHandlerTest.java
index 673f93ae490..eb84a97f46a 100644
---
a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/ServerCapabilitiesHandlerTest.java
+++
b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/ServerCapabilitiesHandlerTest.java
@@ -196,6 +196,7 @@ class ServerCapabilitiesHandlerTest {
assertThat(actualClientSafetyPolicy.get("identity_scope"),
is("mcp_session"));
assertTrue(String.valueOf(actualClientSafetyPolicy.get("transport_scope")).contains("trusted
session attribution"));
assertThat(((Map<?, ?>)
actualClientSafetyPolicy.get("tool_call_limit")).get("scope"), is("session"));
+ assertThat(((Map<?, ?>) ((Map<?, ?>)
actualClientSafetyPolicy.get("runtime_protection")).get("tool_call_limit")).get("scope"),
is("session"));
assertTrue(String.valueOf(actualClientSafetyPolicy.get("abuse_guard")).contains("counted
before dispatch"));
}
diff --git
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/security/MCPClientSafetyPolicy.java
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/security/MCPClientSafetyPolicy.java
index d6f74352886..7af98c3a84a 100644
---
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/security/MCPClientSafetyPolicy.java
+++
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/security/MCPClientSafetyPolicy.java
@@ -19,7 +19,6 @@ package org.apache.shardingsphere.mcp.support.security;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
-import org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -30,9 +29,9 @@ import java.util.Map;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class MCPClientSafetyPolicy {
- public static final int DEFAULT_MAX_TOOL_CALLS_PER_SESSION = 10000;
+ public static final int DEFAULT_MAX_TOOL_CALLS_PER_SESSION =
MCPRuntimeProtectionPolicy.DEFAULT_MAX_TOOL_CALLS_PER_SESSION;
- public static final String MAX_TOOL_CALLS_PER_SESSION_PROPERTY =
"shardingsphere.mcp.maxToolCallsPerSession";
+ public static final String MAX_TOOL_CALLS_PER_SESSION_PROPERTY =
MCPRuntimeProtectionPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY;
/**
* Get maximum tool calls per MCP session.
@@ -40,8 +39,7 @@ public final class MCPClientSafetyPolicy {
* @return maximum tool calls per MCP session
*/
public static int getMaxToolCallsPerSession() {
- Integer configuredValue =
Integer.getInteger(MAX_TOOL_CALLS_PER_SESSION_PROPERTY,
DEFAULT_MAX_TOOL_CALLS_PER_SESSION);
- return configuredValue > 0 ? configuredValue :
DEFAULT_MAX_TOOL_CALLS_PER_SESSION;
+ return MCPRuntimeProtectionPolicy.getMaxToolCallsPerSession();
}
/**
@@ -50,24 +48,16 @@ public final class MCPClientSafetyPolicy {
* @return model-facing safety policy payload
*/
public static Map<String, Object> createModelFacingPayload() {
- Map<String, Object> result = new LinkedHashMap<>(5, 1F);
+ Map<String, Object> result = new LinkedHashMap<>(6, 1F);
result.put("identity_scope", "mcp_session");
result.put("transport_scope",
"HTTP transport can bind trusted session attribution when
configured; "
+ "authentication and authorization remain outside the
runtime in this release. "
+ "STDIO inherits the local process boundary.");
- result.put("tool_call_limit", createToolCallLimitPayload());
+ result.put("tool_call_limit",
MCPRuntimeProtectionPolicy.createToolCallLimitPayload());
+ result.put("runtime_protection",
MCPRuntimeProtectionPolicy.createRuntimeProtectionPayload());
result.put("abuse_guard", "Every tool call is counted before dispatch,
including invalid calls, so runaway model loops stop at the session quota.");
result.put("external_model_boundary", "The MCP runtime never calls
external model providers; live LLM E2E clients call configured endpoints
outside the server.");
return result;
}
-
- private static Map<String, Object> createToolCallLimitPayload() {
- Map<String, Object> result = new LinkedHashMap<>(4, 1F);
- result.put("scope", "session");
- result.put("max_calls", getMaxToolCallsPerSession());
- result.put("property", MAX_TOOL_CALLS_PER_SESSION_PROPERTY);
- result.put(MCPPayloadFieldNames.RECOVERY, "Close and recreate the MCP
session after the quota is exhausted.");
- return result;
- }
}
diff --git
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/security/MCPClientSafetyPolicy.java
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/security/MCPRuntimeProtectionPolicy.java
similarity index 50%
copy from
mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/security/MCPClientSafetyPolicy.java
copy to
mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/security/MCPRuntimeProtectionPolicy.java
index d6f74352886..352ad15ec38 100644
---
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/security/MCPClientSafetyPolicy.java
+++
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/security/MCPRuntimeProtectionPolicy.java
@@ -17,23 +17,31 @@
package org.apache.shardingsphere.mcp.support.security;
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
import org.apache.shardingsphere.mcp.support.protocol.MCPPayloadFieldNames;
import java.util.LinkedHashMap;
import java.util.Map;
/**
- * MCP client-facing safety policy.
+ * MCP runtime protection policy.
*/
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public final class MCPClientSafetyPolicy {
+public final class MCPRuntimeProtectionPolicy {
public static final int DEFAULT_MAX_TOOL_CALLS_PER_SESSION = 10000;
public static final String MAX_TOOL_CALLS_PER_SESSION_PROPERTY =
"shardingsphere.mcp.maxToolCallsPerSession";
+ public static final int DEFAULT_MAX_ROWS = 100;
+
+ public static final int MAX_ROWS_LIMIT = 5000;
+
+ public static final int DEFAULT_TIMEOUT_MILLISECONDS = 0;
+
+ public static final int MAX_TIMEOUT_MILLISECONDS = 300000;
+
+ private MCPRuntimeProtectionPolicy() {
+ }
+
/**
* Get maximum tool calls per MCP session.
*
@@ -45,24 +53,11 @@ public final class MCPClientSafetyPolicy {
}
/**
- * Create model-facing safety policy payload.
+ * Create tool call limit payload.
*
- * @return model-facing safety policy payload
+ * @return tool call limit payload
*/
- public static Map<String, Object> createModelFacingPayload() {
- Map<String, Object> result = new LinkedHashMap<>(5, 1F);
- result.put("identity_scope", "mcp_session");
- result.put("transport_scope",
- "HTTP transport can bind trusted session attribution when
configured; "
- + "authentication and authorization remain outside the
runtime in this release. "
- + "STDIO inherits the local process boundary.");
- result.put("tool_call_limit", createToolCallLimitPayload());
- result.put("abuse_guard", "Every tool call is counted before dispatch,
including invalid calls, so runaway model loops stop at the session quota.");
- result.put("external_model_boundary", "The MCP runtime never calls
external model providers; live LLM E2E clients call configured endpoints
outside the server.");
- return result;
- }
-
- private static Map<String, Object> createToolCallLimitPayload() {
+ public static Map<String, Object> createToolCallLimitPayload() {
Map<String, Object> result = new LinkedHashMap<>(4, 1F);
result.put("scope", "session");
result.put("max_calls", getMaxToolCallsPerSession());
@@ -70,4 +65,43 @@ public final class MCPClientSafetyPolicy {
result.put(MCPPayloadFieldNames.RECOVERY, "Close and recreate the MCP
session after the quota is exhausted.");
return result;
}
+
+ /**
+ * Create runtime protection payload.
+ *
+ * @return runtime protection payload
+ */
+ public static Map<String, Object> createRuntimeProtectionPayload() {
+ Map<String, Object> result = new LinkedHashMap<>(2, 1F);
+ result.put("tool_call_limit", createToolCallLimitPayload());
+ result.put("sql_execution_limits", createSQLExecutionLimitsPayload());
+ return result;
+ }
+
+ private static Map<String, Object> createSQLExecutionLimitsPayload() {
+ Map<String, Object> result = new LinkedHashMap<>(2, 1F);
+ result.put("max_rows", createMaxRowsPayload());
+ result.put("timeout_ms", createTimeoutPayload());
+ return result;
+ }
+
+ private static Map<String, Object> createMaxRowsPayload() {
+ Map<String, Object> result = new LinkedHashMap<>(5, 1F);
+ result.put("default_value", DEFAULT_MAX_ROWS);
+ result.put("maximum_value", MAX_ROWS_LIMIT);
+ result.put("applied_field", "applied_max_rows");
+ result.put("truncation_field", "truncated");
+ result.put(MCPPayloadFieldNames.RECOVERY, "Retry with a narrower
SELECT, stronger WHERE clause, or smaller projection when rows are truncated.");
+ return result;
+ }
+
+ private static Map<String, Object> createTimeoutPayload() {
+ Map<String, Object> result = new LinkedHashMap<>(5, 1F);
+ result.put("default_value", DEFAULT_TIMEOUT_MILLISECONDS);
+ result.put("maximum_value", MAX_TIMEOUT_MILLISECONDS);
+ result.put("applied_field", "applied_timeout_ms");
+ result.put("zero_means", "server_default");
+ result.put(MCPPayloadFieldNames.RECOVERY, "Retry with a bounded
timeout_ms value or omit timeout_ms to use the server default.");
+ return result;
+ }
}
diff --git
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPModelFirstContractPayloadBuilderTest.java
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPModelFirstContractPayloadBuilderTest.java
index 93a67c66645..450a78cef04 100644
---
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPModelFirstContractPayloadBuilderTest.java
+++
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPModelFirstContractPayloadBuilderTest.java
@@ -87,6 +87,7 @@ class MCPModelFirstContractPayloadBuilderTest {
assertThat(actualClientSafetyPolicy.get("identity_scope"),
is("mcp_session"));
assertTrue(String.valueOf(actualClientSafetyPolicy.get("transport_scope")).contains("trusted
session attribution"));
assertThat(castToMap(actualClientSafetyPolicy.get("tool_call_limit")).get("scope"),
is("session"));
+
assertThat(castToMap(castToMap(actualClientSafetyPolicy.get("runtime_protection")).get("tool_call_limit")).get("scope"),
is("session"));
assertTrue(String.valueOf(actualClientSafetyPolicy.get("external_model_boundary")).contains("never
calls external model providers"));
}
diff --git
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/security/MCPClientSafetyPolicyTest.java
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/security/MCPClientSafetyPolicyTest.java
index 8e910d02d25..e5b66a41b9b 100644
---
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/security/MCPClientSafetyPolicyTest.java
+++
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/security/MCPClientSafetyPolicyTest.java
@@ -18,8 +18,12 @@
package org.apache.shardingsphere.mcp.support.security;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import java.util.Map;
+import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -27,14 +31,17 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class MCPClientSafetyPolicyTest {
- @Test
- void assertGetMaxToolCallsPerSession() {
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("assertGetMaxToolCallsPerSessionCases")
+ void assertGetMaxToolCallsPerSession(final String name, final String
configuredValue, final int expectedMaxToolCallsPerSession) {
String previous =
System.getProperty(MCPClientSafetyPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY);
try {
-
System.setProperty(MCPClientSafetyPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY,
"8");
- assertThat(MCPClientSafetyPolicy.getMaxToolCallsPerSession(),
is(8));
-
System.setProperty(MCPClientSafetyPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY,
"0");
- assertThat(MCPClientSafetyPolicy.getMaxToolCallsPerSession(),
is(MCPClientSafetyPolicy.DEFAULT_MAX_TOOL_CALLS_PER_SESSION));
+ if (null == configuredValue) {
+
System.clearProperty(MCPClientSafetyPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY);
+ } else {
+
System.setProperty(MCPClientSafetyPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY,
configuredValue);
+ }
+ assertThat(MCPClientSafetyPolicy.getMaxToolCallsPerSession(),
is(expectedMaxToolCallsPerSession));
} finally {
restoreProperty(previous);
}
@@ -49,6 +56,18 @@ class MCPClientSafetyPolicyTest {
Map<?, ?> actualToolCallLimit = (Map<?, ?>)
actual.get("tool_call_limit");
assertThat(actualToolCallLimit.get("scope"), is("session"));
assertThat(actualToolCallLimit.get("property"),
is(MCPClientSafetyPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY));
+ Map<?, ?> actualRuntimeProtection = (Map<?, ?>)
actual.get("runtime_protection");
+ assertThat(((Map<?, ?>)
actualRuntimeProtection.get("tool_call_limit")).get("scope"), is("session"));
+ Map<?, ?> actualSQLExecutionLimits = (Map<?, ?>)
actualRuntimeProtection.get("sql_execution_limits");
+ assertThat(((Map<?, ?>)
actualSQLExecutionLimits.get("max_rows")).get("applied_field"),
is("applied_max_rows"));
+ assertThat(((Map<?, ?>)
actualSQLExecutionLimits.get("timeout_ms")).get("applied_field"),
is("applied_timeout_ms"));
+ }
+
+ private static Stream<Arguments> assertGetMaxToolCallsPerSessionCases() {
+ return Stream.of(
+ Arguments.of("configured value", "8", 8),
+ Arguments.of("non-positive value falls back to default", "0",
MCPClientSafetyPolicy.DEFAULT_MAX_TOOL_CALLS_PER_SESSION),
+ Arguments.of("missing value falls back to default", null,
MCPClientSafetyPolicy.DEFAULT_MAX_TOOL_CALLS_PER_SESSION));
}
private void restoreProperty(final String previous) {
diff --git
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/security/MCPRuntimeProtectionPolicyTest.java
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/security/MCPRuntimeProtectionPolicyTest.java
new file mode 100644
index 00000000000..8da426a91a9
--- /dev/null
+++
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/security/MCPRuntimeProtectionPolicyTest.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.support.security;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class MCPRuntimeProtectionPolicyTest {
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("assertGetMaxToolCallsPerSessionCases")
+ void assertGetMaxToolCallsPerSession(final String name, final String
configuredValue, final int expectedMaxToolCallsPerSession) {
+ String previous =
System.getProperty(MCPRuntimeProtectionPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY);
+ try {
+ if (null == configuredValue) {
+
System.clearProperty(MCPRuntimeProtectionPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY);
+ } else {
+
System.setProperty(MCPRuntimeProtectionPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY,
configuredValue);
+ }
+ assertThat(MCPRuntimeProtectionPolicy.getMaxToolCallsPerSession(),
is(expectedMaxToolCallsPerSession));
+ } finally {
+ restoreProperty(previous);
+ }
+ }
+
+ @Test
+ void assertCreateToolCallLimitPayload() {
+ String previous =
System.getProperty(MCPRuntimeProtectionPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY);
+ try {
+
System.setProperty(MCPRuntimeProtectionPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY,
"9");
+ Map<String, Object> actual =
MCPRuntimeProtectionPolicy.createToolCallLimitPayload();
+ assertThat(actual.get("scope"), is("session"));
+ assertThat(actual.get("property"),
is(MCPRuntimeProtectionPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY));
+ assertThat(actual.get("max_calls"), is(9));
+ assertThat(actual.get("recovery"), is("Close and recreate the MCP
session after the quota is exhausted."));
+ } finally {
+ restoreProperty(previous);
+ }
+ }
+
+ @Test
+ void assertCreateRuntimeProtectionPayload() {
+ Map<String, Object> actual =
MCPRuntimeProtectionPolicy.createRuntimeProtectionPayload();
+ Map<?, ?> actualToolCallLimit = (Map<?, ?>)
actual.get("tool_call_limit");
+ assertThat(actualToolCallLimit.get("scope"), is("session"));
+ Map<?, ?> actualSQLExecutionLimits = (Map<?, ?>)
actual.get("sql_execution_limits");
+ Map<?, ?> actualMaxRows = (Map<?, ?>)
actualSQLExecutionLimits.get("max_rows");
+ assertThat(actualMaxRows.get("default_value"),
is(MCPRuntimeProtectionPolicy.DEFAULT_MAX_ROWS));
+ assertThat(actualMaxRows.get("maximum_value"),
is(MCPRuntimeProtectionPolicy.MAX_ROWS_LIMIT));
+ assertThat(actualMaxRows.get("applied_field"), is("applied_max_rows"));
+ assertThat(actualMaxRows.get("truncation_field"), is("truncated"));
+ Map<?, ?> actualTimeout = (Map<?, ?>)
actualSQLExecutionLimits.get("timeout_ms");
+ assertThat(actualTimeout.get("default_value"),
is(MCPRuntimeProtectionPolicy.DEFAULT_TIMEOUT_MILLISECONDS));
+ assertThat(actualTimeout.get("maximum_value"),
is(MCPRuntimeProtectionPolicy.MAX_TIMEOUT_MILLISECONDS));
+ assertThat(actualTimeout.get("applied_field"),
is("applied_timeout_ms"));
+ assertThat(actualTimeout.get("zero_means"), is("server_default"));
+ }
+
+ private static Stream<Arguments> assertGetMaxToolCallsPerSessionCases() {
+ return Stream.of(
+ Arguments.of("configured value", "8", 8),
+ Arguments.of("non-positive value falls back to default", "0",
MCPRuntimeProtectionPolicy.DEFAULT_MAX_TOOL_CALLS_PER_SESSION),
+ Arguments.of("missing value falls back to default", null,
MCPRuntimeProtectionPolicy.DEFAULT_MAX_TOOL_CALLS_PER_SESSION));
+ }
+
+ private void restoreProperty(final String previous) {
+ if (null == previous) {
+
System.clearProperty(MCPRuntimeProtectionPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY);
+ } else {
+
System.setProperty(MCPRuntimeProtectionPolicy.MAX_TOOL_CALLS_PER_SESSION_PROPERTY,
previous);
+ }
+ }
+}