apache/shenyu · error · IllegalStateException

Tool definition must be of type ShenyuToolDefinition, got

Error message

Tool definition must be of type ShenyuToolDefinition, got: ${toolDefinition.getClass().getSimpleName()}

What it means

ShenyuToolCallback requires its ToolDefinition to be a ShenyuToolDefinition (the ShenYu-specific subtype exposing requestConfig). validateToolDefinition throws IllegalStateException if the injected definition is a plain or foreign ToolDefinition implementation, because the request-config extraction would otherwise fail.

Solutions

  1. Create tool specs using ShenyuToolDefinition (its builder) so the definition passed to ShenyuToolCallback is the right type.
  2. Check how the tool specification is registered and ensure no adapter converts it to a base ToolDefinition.
  3. After dependency upgrades, confirm the MCP integration still constructs ShenyuToolDefinition instances.

Example fix

// before
ToolDefinition def = ToolDefinition.builder().name("t").build();
// after
ShenyuToolDefinition def = ShenyuToolDefinition.builder().name("t").requestConfig(configJson).build();
Defensive patterns

Strategy: type-guard

Type guard

ShenyuToolDefinition asShenyuToolDefinition(ToolDefinition d) {
    if (d instanceof ShenyuToolDefinition s) {
        return s;
    }
    throw new IllegalArgumentException("Expected ShenyuToolDefinition, got " + d.getClass().getName());
}

Try / catch

try {
    return callback.call(args, ctx);
} catch (IllegalStateException e) {
    if (e.getMessage().startsWith("Tool definition must be")) {
        // rebuild tool spec with ShenyuToolDefinition and retry registration
    }
    throw e;
}

Prevention

When it happens

Trigger: Constructing ShenyuToolCallback with a McpServerFeature.SyncToolSpecification or Spring AI ToolDefinition that is not an instance of ShenyuToolDefinition — typically when a tool is registered by code that builds a generic ToolDefinition instead of ShenyuToolDefinition.

Common situations: Registering MCP tools programmatically with a hand-rolled ToolDefinition; upgrading Spring AI/MCP SDK so the callback receives a differently-typed definition; forgetting to wrap the spec via the Shenyu builder.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12). Data as JSON: /api/errors/26546ac536462d05. Report an issue: GitHub.

Appendix: source

Thrown at shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java:172

            // Execute the tool call through the plugin chain
            return executeToolCall(originExchange, chain, sessionId, configStr, input);

        } catch (Exception e) {
            LOG.error("Failed to process tool call for '{}': {}", toolDefinition.name(), e.getMessage(), e);

            throw new RuntimeException("Tool execution failed: " + e.getMessage(), e);
        }
    }

    /**
     * Validates and casts the tool definition to Shenyu-specific type.
     *
     * @return the Shenyu tool definition
     * @throws IllegalStateException if tool definition is not of expected type
     */
    private ShenyuToolDefinition validateToolDefinition() {
        if (!(this.toolDefinition instanceof ShenyuToolDefinition)) {
            throw new IllegalStateException("Tool definition must be of type ShenyuToolDefinition, got: "
                    + this.toolDefinition.getClass().getSimpleName());
        }
        return (ShenyuToolDefinition) this.toolDefinition;
    }

    /**
     * Extracts and validates the request configuration from the tool definition.
     *
     * @param definition the Shenyu tool definition
     * @return the request configuration string
     * @throws IllegalStateException if configuration is missing or invalid
     */
    private String extractRequestConfig(final ShenyuToolDefinition definition) {
        final String config = definition.requestConfig();
        if (!StringUtils.hasText(config)) {
            throw new IllegalStateException("Request configuration cannot be empty");
        }
        LOG.debug("Using request configuration with length: {} chars", config.length());

View on GitHub (pinned to 567142e072)