apache/shardingsphere · error · MCPUnsupportedSQLStatementException

Statement is not supported by the MCP contract.

Error message

Statement is not supported by the MCP contract.

What it means

In MCPStatementAnalyzer.analyze, a statement that parses successfully but turns out to be a TCLStatement (transaction control other than the recognized leading-keyword forms handled earlier, e.g. SET TRANSACTION or dialect-specific transaction SQL) throws MCPUnsupportedSQLStatementException ('Statement is not supported by the MCP contract.'). Only BEGIN/START TRANSACTION/COMMIT/ROLLBACK and SAVEPOINT forms recognized by the leading-keyword fast paths are supported; other TCL shapes fall through to this guard.

Source

Thrown at mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPStatementAnalyzer.java:82

    
    ClassificationResult analyze(final String sql, final MCPDatabaseCapability databaseCapability) {
        String databaseType = databaseCapability.getDatabaseType();
        SQLStatementScanner scanner = new SQLStatementScanner(databaseType, sql);
        String actualSql = scanner.sql();
        String leadingSql = scanner.leadingSql();
        String upperLeadingSql = leadingSql.toUpperCase(Locale.ENGLISH);
        SQLStatementSafetyValidator safetyValidator = new SQLStatementSafetyValidator();
        safetyValidator.checkLeadingStatement(upperLeadingSql, scanner.containsExecutableComment());
        if (isSavepointStatement(upperLeadingSql)) {
            return analyzeSavepointStatement(actualSql, leadingSql, upperLeadingSql);
        }
        if (isTransactionControlStatement(upperLeadingSql)) {
            return createTCLResult(SupportedMCPStatement.TRANSACTION_CONTROL, extractTransactionStatementType(upperLeadingSql), actualSql, "");
        }
        SQLStatement sqlStatement = parse(actualSql, databaseType);
        safetyValidator.checkParsedStatement(sqlStatement);
        if (sqlStatement instanceof TCLStatement) {
            throw new MCPUnsupportedSQLStatementException();
        }
        boolean ruleDistSQL = isRuleDistSQL(sqlStatement);
        String leadingKeyword = scanner.extractLeadingKeyword();
        SupportedMCPStatement statementClass = resolveStatementClass(sqlStatement, leadingKeyword, ruleDistSQL);
        String statementType = resolveStatementType(sqlStatement, statementClass, leadingKeyword);
        return new ClassificationResult(statementClass, statementType, actualSql, "", new SQLStatementObjectExtractor().extract(sqlStatement), ruleDistSQL);
    }
    
    private ClassificationResult analyzeSavepointStatement(final String actualSql, final String leadingSql, final String upperLeadingSql) {
        String savepointName = extractSavepointName(leadingSql);
        ShardingSpherePreconditions.checkState(!savepointName.isEmpty(), () -> new MCPInvalidRequestException("Savepoint name is required."));
        return createTCLResult(SupportedMCPStatement.SAVEPOINT, extractTransactionStatementType(upperLeadingSql), actualSql, savepointName);
    }
    
    private ClassificationResult createTCLResult(final SupportedMCPStatement statementClass, final String statementType, final String sql, final String savepointName) {
        return new ClassificationResult(statementClass, statementType, sql, savepointName, Set.of(), false);
    }
    

View on GitHub (pinned to e952770a21)

Solutions

  1. Remove SET TRANSACTION / session-configuration statements from the SQL sent to MCP execution tools.
  2. Manage isolation and transaction boundaries through the dedicated transaction control tool/path (COMMIT/ROLLBACK/SAVEPOINT forms).
  3. Configure isolation at the datasource level instead of per-statement.

Example fix

// before
await tools.call('database_gateway_execute_update', { sql: 'SET TRANSACTION ISOLATION LEVEL READ COMMITTED', execution_mode: 'execute' }); // unsupported

// after: set isolation on the datasource, not via MCP SQL
await tools.call('database_gateway_execute_query', { sql: 'SELECT * FROM t' });
Defensive patterns

Strategy: validation

Validate before calling

const UNSUPPORTED_TCL = /^\s*SET\s+(TRANSACTION|SESSION|CHARACTERISTICS|CONSTRAINT|CONSTRAINTS)\b/i;
if (UNSUPPORTED_TCL.test(sql)) {
  throw new Error('Session/transaction configuration SQL is unsupported; configure the datasource instead.');
}
return tools.call('database_gateway_execute_update', { sql, execution_mode });

Type guard

function isSupportedTransactionSql(sql) {
  return /^\s*(BEGIN(\s+WORK)?|START\s+TRANSACTION|COMMIT(\s+WORK)?|ROLLBACK(\s+WORK)?(\s+TO\s+SAVEPOINT\s+\S+)?|SAVEPOINT\s+\S+|RELEASE\s+SAVEPOINT\s+\S+)\s*;?\s*$/i.test(sql);
}

Try / catch

try {
  return await tools.call('database_gateway_execute_update', { sql, execution_mode });
} catch (e) {
  if (/not supported by the MCP contract/.test(e.message) && /^\s*SET\s/i.test(sql)) {
    // drop the SET TRANSACTION statement; move setting to datasource config
    return { skipped: sql };
  }
  throw e;
}

Prevention

When it happens

Trigger: Sending SET TRANSACTION ..., SET SESSION CHARACTERISTICS, or other parseable TCL statements that bypass isTransactionControlStatement/isSavepointStatement keyword checks and surface as parsed TCLStatement instances.

Common situations: Boilerplate connection scripts that issue SET TRANSACTION ISOLATION LEVEL ...; ORM-generated session setup SQL; copying transaction management SQL into MCP tool calls instead of using the transaction tools.

Related errors


AI-assisted analysis of apache/shardingsphere@e952770a21 (2026-08-14). Data as JSON: /api/errors/81284ee6235d522e. Report an issue: GitHub.