{"record":{"id":"dde5e1cde9dae11d","repo":"apache/shardingsphere","slug":"database-gateway-execute-update-does-not-accept-re","errorCode":null,"errorMessage":"database_gateway_execute_update does not accept read-only SQL. Use database_gateway_execute_query for read-only SQL.","messagePattern":"database_gateway_execute_update does not accept read-only SQL\\. Use database_gateway_execute_query for read-only SQL\\.","errorType":"validation","errorClass":"SQLToolMismatchException","httpStatus":null,"severity":"error","filePath":"mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandler.java","lineNumber":90,"sourceCode":"    \n    @Override\n    public MCPSuccessPayload handle(final MCPFeatureRequestContext requestContext, final Map<String, Object> arguments) {\n        MCPToolArguments toolArguments = new MCPToolArguments(arguments);\n        String executionMode = resolveExecutionMode(toolArguments);\n        SQLExecutionToolHandlerSupport.checkExecutionArguments(toolArguments, CoreToolNames.EXECUTE_UPDATE);\n        String sql = toolArguments.getStringArgument(\"sql\");\n        ClassificationResult classificationResult = checkUpdateStatement(requestContext, toolArguments, sql);\n        if (EXECUTION_MODE_PREVIEW.equals(executionMode)) {\n            return createPreviewResponse(toolArguments, classificationResult);\n        }\n        return SQLExecutionPayload.executed(requestContext.getExecutionFacade().execute(\n                SQLExecutionToolHandlerSupport.createExecutionRequest(requestContext.getSessionIdentity().getSessionId(), toolArguments, sql, CoreToolNames.EXECUTE_UPDATE)));\n    }\n    \n    private ClassificationResult checkUpdateStatement(final MCPFeatureRequestContext requestContext, final MCPToolArguments toolArguments, final String sql) {\n        ClassificationResult classificationResult = SQLExecutionToolHandlerSupport.analyze(requestContext, toolArguments, sql);\n        if (SQLExecutionToolHandlerSupport.isQueryStatement(classificationResult)) {\n            throw new SQLToolMismatchException(\"database_gateway_execute_update does not accept read-only SQL. Use database_gateway_execute_query for read-only SQL.\",\n                    CoreToolNames.EXECUTE_UPDATE, CoreToolNames.EXECUTE_QUERY, classificationResult,\n                    createQuerySuggestedArguments(toolArguments, classificationResult));\n        }\n        return classificationResult;\n    }\n    \n    private String resolveExecutionMode(final MCPToolArguments toolArguments) {\n        String result = toolArguments.getStringArgument(MCPPayloadFieldNames.EXECUTION_MODE);\n        if (result.isEmpty()) {\n            throw new MCPExecutionModeRequiredException(CoreToolNames.EXECUTE_UPDATE, EXECUTION_MODES, createPreviewSuggestedArguments(toolArguments));\n        }\n        if (EXECUTION_MODE_EXECUTE.equals(result) || EXECUTION_MODE_PREVIEW.equals(result)) {\n            return result;\n        }\n        throw new MCPInvalidExecutionModeException(CoreToolNames.EXECUTE_UPDATE, EXECUTION_MODES, createPreviewSuggestedArguments(toolArguments));\n    }\n    \n    private MCPSuccessPayload createPreviewResponse(final MCPToolArguments toolArguments, final ClassificationResult classificationResult) {","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/apache/shardingsphere/blob/e952770a215630a3659c75d64369168cd3e26b82/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandler.java#L72-L108","documentation":"The mirror of the execute_query guard: database_gateway_execute_update classifies the SQL first, and if it is a query statement it throws SQLToolMismatchException telling the caller to use database_gateway_execute_query. This prevents using the side-effecting tool (and its preview/execute modes) for read-only SQL, keeping read and write paths separate in the MCP contract. The exception includes suggested arguments pointing at execute_query.","triggerScenarios":"Calling database_gateway_execute_update (any execution_mode) with SELECT, WITH-CTE reads, or other parser-approved query statements.","commonSituations":"Agent defaulting to the 'update' tool for all SQL; scripts that always pass execution_mode:'execute' regardless of statement type; EXPLAIN-free reports mistakenly sent through the update path.","solutions":["Send read-only statements to database_gateway_execute_query instead.","Pre-classify SQL client-side by leading keyword and pick the tool accordingly.","Use the exception's suggested arguments to reroute to execute_query automatically.","For EXPLAIN, use database_gateway_execute_explain_query."],"exampleFix":"// before\nawait tools.call('database_gateway_execute_update', { sql: 'SELECT * FROM t', execution_mode: 'execute' }); // SQLToolMismatchException\n\n// after\nawait tools.call('database_gateway_execute_query', { sql: 'SELECT * FROM t' });","handlingStrategy":"validation","validationCode":"const isReadOnly = /^\\s*(SELECT|WITH)\\b/i.test(sql) && !/\\b(INSERT|UPDATE|DELETE|MERGE)\\b/i.test(sql);\nif (isReadOnly) {\n  return tools.call('database_gateway_execute_query', { sql });\n}\nreturn tools.call('database_gateway_execute_update', { sql, execution_mode: mode });","typeGuard":"function isQueryStatementSql(sql) {\n  const upper = sql.trimStart().toUpperCase();\n  if (upper.startsWith('SELECT')) return true;\n  return upper.startsWith('WITH') && !/\\b(INSERT|UPDATE|DELETE|MERGE)\\b/.test(upper);\n}","tryCatchPattern":"try {\n  return await tools.call('database_gateway_execute_update', { sql, execution_mode: mode });\n} catch (e) {\n  if (e.name === 'SQLToolMismatchException' && e.suggestedTool === 'database_gateway_execute_query') {\n    return tools.call('database_gateway_execute_query', { sql });\n  }\n  throw e;\n}","preventionTips":["Default SELECT/WITH reads to execute_query; reserve execute_update for mutations.","Use the mismatch exception's suggested tool/arguments for automatic rerouting.","Include execution_mode only on the update tool.","Test the router against your dialect's statement forms."],"tags":["mcp","sql","tool-routing","classification","validation"],"backgroundTag":null,"analyzedSha":"e952770a215630a3659c75d64369168cd3e26b82","analyzedAt":"2026-08-14T13:54:53.392Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}