spring-projects/spring-ai · error · RuntimeException
Failed to access tool method
Error message
Failed to access tool method
What it means
AbstractMcpToolMethodCallback.callMethod throws RuntimeException('Failed to access tool method', cause IllegalAccessException) when the reflective toolMethod.invoke call is denied — the JVM cannot access the (possibly non-public) method even after setAccessible(true), typically due to module restrictions or visibility issues. The original IllegalAccessException is preserved as the cause.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/tool/AbstractMcpToolMethodCallback.java:80
this.toolMethod = toolMethod;
this.toolObject = toolObject;
this.returnMode = returnMode;
}
/**
* Invokes the tool method with the provided arguments.
* @param methodArguments The arguments to pass to the method
* @return The result of the method invocation
* @throws IllegalStateException if the method cannot be accessed
* @throws RuntimeException if there's an error invoking the method
*/
protected Object callMethod(Object[] methodArguments) {
this.toolMethod.setAccessible(true);
try {
return this.toolMethod.invoke(this.toolObject, methodArguments);
}
catch (IllegalAccessException ex) {
throw new RuntimeException("Failed to access tool method", ex);
}
catch (InvocationTargetException ex) {
Throwable cause = ex.getCause();
if (cause instanceof RuntimeException re) {
throw re;
}
if (cause instanceof Error err) {
throw err;
}
throw new UndeclaredThrowableException(cause);
}
}
/**
* Builds the method arguments from the context, tool input arguments, and optionally
* the full request.
* @param exchangeOrContext The exchange or context object (e.g.,
* McpSyncServerExchange, McpAsyncServerExchange, or McpTransportContext)View on GitHub (pinned to 98a7beda4f)
Solutions
- Make the tool method public (and its class public)
- Add JVM args like --add-opens=<module>/<package>=ALL-UNNAMED for the encapsulated package
- Keep tool beans on the classpath (unnamed module) rather than inside named modules
- Check for SecurityManager/policy blocks on reflective access
Example fix
// before
private String myTool(String arg) { ... }
// after
public String myTool(String arg) { ... } Defensive patterns
Strategy: try-catch
Try / catch
try {
result = callback.call(request);
} catch (RuntimeException e) {
if (e.getCause() instanceof IllegalAccessException iae) {
log.error("Tool method not accessible: {}", iae.getMessage(), iae);
}
throw e;
} Prevention
- Declare tool methods public in public classes
- Avoid exposing tools from strongly encapsulated JDK/internal modules
- Add --add-opens flags when reflection on named modules is unavoidable
When it happens
Trigger: Invoking a private/package-private tool method from a different package or a Java module that does not open its package to the framework; running under JPMS or a hardened SecurityManager/agent policy.
Common situations: Tool methods on classes in java.* or other strongly-encapsulated modules; JDK 16+ strong encapsulation breaking reflection on internal APIs; tool beans in modularized libraries.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- UndeclaredThrowableException
- Neither createIndex() nor ensureIndex() method found on Inde
- Failed to invoke ensureIndex() method
- Failed to invoke createIndex() method
- Required no-arg constructor not found in
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/82c61fd2c61a4f77.
Report an issue: GitHub.