apache/shardingsphere · error · MCPBannedSQLStatementException
Statement is banned by the MCP contract.
Error message
Statement is banned by the MCP contract.
What it means
SQLStatementSafetyValidator.checkLeadingStatement bans entire statement families before parsing: executable comments, and SQL starting with USE, SET, COPY, LOAD, CALL, or ALTER SYSTEM throw MCPBannedSQLStatementException ('Statement is banned by the MCP contract.'). These statements alter session/connection state, bulk-load data, invoke procedures, or reconfigure the server, so they are excluded outright (note metadata-introspection statements get their own distinct exception). The walker also inspects parsed statements for banned types and banned side-effecting functions.
Source
Thrown at mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementSafetyValidator.java:54
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
import java.util.List;
import java.util.Locale;
final class SQLStatementSafetyValidator {
private static final List<String> SIDE_EFFECTING_FUNCTION_NAMES = List.of("NEXTVAL", "NEXT VALUE FOR", "SETVAL", "GET_LOCK", "RELEASE_LOCK", "RELEASE_ALL_LOCKS",
"PG_ADVISORY_LOCK", "PG_ADVISORY_XACT_LOCK", "PG_TRY_ADVISORY_LOCK", "PG_TRY_ADVISORY_XACT_LOCK", "PG_ADVISORY_UNLOCK", "PG_ADVISORY_UNLOCK_ALL",
"SET_CONFIG", "PG_REPLICATION_SLOT_ADVANCE", "PG_LOGICAL_SLOT_GET_CHANGES", "PG_LOGICAL_SLOT_GET_BINARY_CHANGES", "PG_LOGICAL_EMIT_MESSAGE", "PG_SWITCH_WAL",
"PG_RELOAD_CONF", "PG_CANCEL_BACKEND", "PG_TERMINATE_BACKEND");
private static final List<String> METADATA_LOOKUP_FUNCTION_NAMES = List.of("TO_REGCLASS", "TO_REGTYPE", "TO_REGPROC", "TO_REGPROCEDURE", "TO_REGOPER",
"TO_REGOPERATOR", "TO_REGNAMESPACE", "TO_REGROLE", "OBJECT_ID");
void checkLeadingStatement(final String upperSql, final boolean executableComment) {
if (executableComment || upperSql.startsWith("USE ") || upperSql.startsWith("SET ") || upperSql.startsWith("COPY ") || upperSql.startsWith("LOAD ")
|| upperSql.startsWith("CALL ") || isAlterSystemStatement(upperSql)) {
throw new MCPBannedSQLStatementException();
}
if (isMetadataIntrospectionStatement(upperSql)) {
throw new MetadataIntrospectionSQLStatementException(extractStatementType(upperSql));
}
}
void checkParsedStatement(final SQLStatement sqlStatement) {
new SQLStatementTreeWalker(this::checkStatement, this::checkExpression).walk(sqlStatement);
}
private boolean isBannedStatementType(final SQLStatement sqlStatement) {
return sqlStatement instanceof SetStatement || sqlStatement instanceof CallStatement
|| sqlStatement instanceof CreateUserStatement || sqlStatement instanceof AlterUserStatement || sqlStatement instanceof DropUserStatement
|| sqlStatement instanceof CreateRoleStatement || sqlStatement instanceof AlterRoleStatement || sqlStatement instanceof DropRoleStatement;
}
private boolean containsExecutableComment(final SQLStatement sqlStatement) {
for (CommentSegment each : sqlStatement.getComments()) {View on GitHub (pinned to e952770a21)
Solutions
- Remove SET/USE/COPY/LOAD/CALL/ALTER SYSTEM statements from MCP-bound SQL; configure session settings at the datasource.
- Select databases/schemas via the tool's database and schema arguments instead of USE.
- Strip executable comment directives (/*! ... */) from dump-derived SQL before sending.
- Invoke stored procedures through your own database client, not the MCP tools.
Example fix
// before
await tools.call('database_gateway_execute_update', { sql: 'SET FOREIGN_KEY_CHECKS=0; TRUNCATE t;', execution_mode: 'execute' }); // banned leading SET
// after
await tools.call('database_gateway_execute_update', { sql: 'TRUNCATE t', execution_mode: 'execute' }); // FK checks managed on the datasource Defensive patterns
Strategy: validation
Validate before calling
const BANNED_PREFIX_RE = /^\s*(USE|SET|COPY|LOAD|CALL)\b|^\s*ALTER\s+SYSTEM\b/i;
const EXECUTABLE_COMMENT_RE = /\/\*!|\/\*\s*\+/;
function screenBannedSql(sql) {
const stripped = sql.replace(/\/\*[!+]?.*?\*\//gs, '').trim();
if (EXECUTABLE_COMMENT_RE.test(sql)) throw new Error('Executable comments are banned by the MCP contract');
if (BANNED_PREFIX_RE.test(stripped)) throw new Error(`Statement family banned by the MCP contract: ${stripped.slice(0, 20)}`);
}
screenBannedSql(sql); Type guard
function isBannedLeadingStatement(sql) {
const s = sql.trimStart().toUpperCase();
return s.startsWith('USE ') || s.startsWith('SET ') || s.startsWith('COPY ') || s.startsWith('LOAD ')
|| s.startsWith('CALL ') || s.startsWith('ALTER SYSTEM ') || /^\/\*[!+]/.test(sql.trimStart());
} Try / catch
try {
return await tools.call('database_gateway_execute_update', { sql, execution_mode });
} catch (e) {
if (/banned by the MCP contract/.test(e.message)) {
// strip banned leading statements / executable comments, keep the rest, retry once
const cleaned = sql.replace(/\/\*[!+]?.*?\*\//gs, '').replace(/^\s*(USE|SET|COPY|LOAD|CALL)\b[^;]*;?\s*/i, '');
return cleaned ? tools.call('database_gateway_execute_update', { sql: cleaned, execution_mode }) : undefined;
}
throw e;
} Prevention
- Strip SET/USE/COPY/LOAD/CALL and executable comments from SQL before MCP submission.
- Select schema via tool arguments, never via USE.
- Manage session variables at the datasource level.
- Run stored procedures and bulk loads through your own database client.
When it happens
Trigger: Sending SET foreign_key_checks=0, USE mydb, COPY t FROM stdin, LOAD DATA INFILE ..., CALL proc(), ALTER SYSTEM ..., or SQL prefixed with an executable comment (/*! ... */) to any MCP SQL execution tool.
Common situations: Dump/restore scripts replayed through MCP; ORM session-setup statements (SET names/timeout); stored-procedure invocations; MySQL dumps full of /*!40001 ... */ executable comments; attempting schema switching via USE instead of the schema argument.
Related errors
- database_gateway_execute_query only supports parser-approved
- database_gateway_execute_update does not accept read-only SQ
- Cross-schema SQL is not supported for database `%s`: `%s`.
- Session attribution does not match this MCP session.
- Completion argument `%s` is not declared for %s `%s`.
AI-assisted analysis of apache/shardingsphere@e952770a21 (2026-08-14).
Data as JSON: /api/errors/7f883de31d520ad7.
Report an issue: GitHub.