apache/shardingsphere · error · MetadataIntrospectionSQLStatementException

Metadata introspection SQL should use MCP metadata resources

Error message

Metadata introspection SQL should use MCP metadata resources.

What it means

Thrown by SQLStatementSafetyValidator when an MCP execute-SQL request contains a metadata introspection statement (SHOW, DESCRIBE, or DESC as the leading keyword). The MCP contract forbids using raw SQL for catalog/metadata queries; the server exposes dedicated MCP metadata resources/tools instead, so clients get structured, safe output rather than dialect-specific result sets.

Source

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

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()) {
            String text = each.getText().trim();
            if (text.startsWith("/*!") || text.toUpperCase(Locale.ENGLISH).startsWith("/*M!")) {
                return true;

View on GitHub (pinned to e952770a21)

Solutions

  1. Use the MCP metadata tool/resources (e.g. the object_types-based metadata listing tool) instead of SHOW/DESCRIBE to inspect schemas, tables, and columns
  2. If you only need row data, rewrite the statement as an information-preserving SELECT against user tables
  3. If you maintain the server and must allow it, adjust SQLStatementSafetyValidator.isMetadataIntrospectionStatement — but note this weakens the MCP read-only metadata contract

Example fix

// before
execute_sql("SHOW TABLES")
execute_sql("DESC t_order")

// after
list_metadata({"object_types": ["table"]})
execute_sql("SELECT * FROM t_order LIMIT 1")
Defensive patterns

Strategy: validation

Validate before calling

// Java — reject before calling the MCP execute tool
String upper = sql.strip().toUpperCase(Locale.ENGLISH);
if (upper.equals("SHOW") || upper.startsWith("SHOW ")
        || upper.equals("DESCRIBE") || upper.startsWith("DESCRIBE ")
        || upper.equals("DESC") || upper.startsWith("DESC ")) {
    throw new IllegalArgumentException("Use MCP metadata resources, not SHOW/DESC: " + sql);
}

Try / catch

try {
    executeSql(sql);
} catch (MetadataIntrospectionSQLStatementException e) {
    // fall back to the metadata tool with equivalent scope
    return listMetadata(Map.of("object_types", List.of("table")));
}

Prevention

When it happens

Trigger: checkLeadingStatement() sees upperSql equal to or starting with "SHOW " / "SHOW", "DESCRIBE " / "DESCRIBE", or "DESC " / "DESC" — e.g. `SHOW TABLES`, `DESCRIBE t_order`, `DESC t_order`. Any leading keyword match throws MetadataIntrospectionSQLStatementException with the extracted statement type.

Common situations: LLM agents or scripts habitually call `SHOW TABLES` / `DESC table` to explore schema through the execute_sql MCP tool; porting existing DBA scripts that mix DML with SHOW commands; MySQL workbench-style discovery queries.

Related errors


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