apache/shardingsphere · error · MCPInvalidApprovedStepsException

approved_steps must contain only %s.

Error message

approved_steps must contain only %s.

What it means

Thrown by WorkflowExecutionService.requireApprovedSteps when the approved_steps list on an apply request contains at least one value outside ALLOWED_APPROVAL_STEPS. Null, absent, or empty lists are allowed (no approval filtering); only an unrecognized step name triggers MCPInvalidApprovedStepsException, which carries the allowed set and preview-suggested arguments.

Source

Thrown at mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowExecutionService.java:122

    
    private String requireExecutionMode(final WorkflowContextSnapshot snapshot, final String executionMode) {
        if (executionMode.isEmpty()) {
            throw new MCPExecutionModeRequiredException(WorkflowToolDescriptors.APPLY_TOOL_NAME, EXECUTION_MODES, createPreviewSuggestedArguments(snapshot));
        }
        String result = executionMode.toLowerCase(Locale.ENGLISH);
        if (!EXECUTION_MODES.contains(result)) {
            throw new MCPInvalidExecutionModeException(WorkflowToolDescriptors.APPLY_TOOL_NAME, EXECUTION_MODES, createPreviewSuggestedArguments(snapshot));
        }
        return result;
    }
    
    private void requireApprovedSteps(final WorkflowContextSnapshot snapshot, final List<String> approvedSteps) {
        if (null == approvedSteps || approvedSteps.isEmpty()) {
            return;
        }
        for (String each : approvedSteps) {
            if (!ALLOWED_APPROVAL_STEPS.contains(each)) {
                throw new MCPInvalidApprovedStepsException(ALLOWED_APPROVAL_STEPS, createPreviewSuggestedArguments(snapshot));
            }
        }
    }
    
    private Map<String, Object> checkApplyPreconditions(final String sessionId, final WorkflowContextSnapshot snapshot, final String executionMode,
                                                        final List<String> approvedSteps) {
        if (!WorkflowLifecycleUtils.isOwnedBySession(sessionId, snapshot)) {
            return createRejectedResponse(snapshot, executionMode, WorkflowIssueCode.SESSION_OWNERSHIP_MISMATCH, "The workflow plan belongs to another MCP session.",
                    "Continue the workflow from the same session that created the plan.");
        }
        if (!isApplicableStatus(snapshot)) {
            return createRejectedResponse(snapshot, executionMode, WorkflowIssueCode.WORKFLOW_STATUS_INVALID,
                    String.format("Workflow status `%s` cannot enter apply in the current lifecycle.", snapshot.getStatus()),
                    "Plan the workflow again or continue from a reviewable status.");
        }
        if (WorkflowLifecycle.EXECUTION_MODE_REVIEW_THEN_EXECUTE.equals(executionMode) && !WorkflowLifecycle.STATUS_PREVIEWED.equalsIgnoreCase(snapshot.getStatus())) {
            return createRejectedResponse(snapshot, executionMode, WorkflowIssueCode.WORKFLOW_STATUS_INVALID,
                    "Automatic workflow execution requires an execution_mode=preview call first.",

View on GitHub (pinned to e952770a21)

Solutions

  1. Use only the step identifiers listed in the MCPInvalidApprovedStepsException payload, matching exactly (no case or wording improvisation).
  2. If you do not need step-level approval, omit approved_steps or send an empty list rather than guessing names.
  3. Echo the step identifiers verbatim from the workflow plan artifact the plan tool returned, not from prose summaries.

Example fix

// before
arguments.put("approved_steps", List.of("apply-everything"));
// after
arguments.put("approved_steps", List.of(/* exact identifiers from the plan artifact */));
Defensive patterns

Strategy: validation

Validate before calling

List<String> unknown = approvedSteps.stream().filter(s -> !ALLOWED_APPROVAL_STEPS.contains(s)).toList();
if (!unknown.isEmpty()) { throw new IllegalArgumentException("unknown steps: " + unknown); }

Type guard

const stepsOk = steps => steps == null || steps.every(s => ALLOWED_APPROVAL_STEPS.includes(s));

Try / catch

try {
    applyTool.call(request);
} catch (final MCPInvalidApprovedStepsException ex) {
    // drop invented step names; use only identifiers from the plan artifact or omit approved_steps
}

Prevention

When it happens

Trigger: Sending approved_steps such as ["everything"] or ["APPLY"] when only specific step identifiers (the values in ALLOWED_APPROVAL_STEPS) are accepted. Any single bad entry rejects the whole list.

Common situations: An agent inventing step names to force full auto-apply, mismatched step vocabulary after a workflow engine upgrade, or free-text step labels copied from the plan's human-readable output instead of the machine identifiers.

Related errors


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