Class McpServerToolService
-
Field Summary
Fields -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidaddToolUpdateListener(ToolListener listener) Registers a listener notified whenever a tool definition changes.voidSwitches the MCP server off instance-wide and drops the memoised flag so the change is visible on the next request.voidSwitches the MCP server on instance-wide and drops the memoised flag so the change is visible on the next request.getToolDefinition(String toolName) getToolDefinitionByMethodName(String methodName) booleanisAllowedTool(SimpleToolDefinition toolDefinition, org.springframework.security.core.Authentication authentication) Tells whether the authenticated caller may execute, or even see, one given tool: the MCP server must be globally on, and the token must carry a scope matching the tool's read or write nature.booleanisAllowedTool(String toolOrMethodName, org.springframework.security.core.Authentication authentication) Same check asisAllowedTool(SimpleToolDefinition, Authentication), for a caller holding only a name: an MCP tool name or the Java method name behind it, tried in that order.booleanbooleanisMcpServerEnabledForUser(String username) Answers the per-user MCP question — is MCP globally on and is this user in its audience? — for the places that ask it: the door (McpServerOauthOpaqueTokenIntrospector) and the token endpoint (McpServerOAuthAccessTokenAudienceProvider).booleanisRequireApproval(String methodName, org.springframework.security.core.Authentication authentication) Tells whether a tool call must be confirmed by the end user before it runs.org.springframework.ai.tool.definition.ToolDefinitionupdateToolDefinition(String toolName, String title, String description, String inputSchema, boolean requireApproval, boolean disabled) Updates an administrable tool definition, persists the whole set and notifies listeners so that a running instance picks the change up without a restart.
-
Field Details
-
SCOPE_PATTERN
- See Also:
-
READ_SCOPE_AUTHORITY
-
WRITE_SCOPE_AUTHORITY
-
WRITE_APPROVE_SCOPE_AUTHORITY
-
-
Constructor Details
-
McpServerToolService
public McpServerToolService()
-
-
Method Details
-
getToolDefinitionByMethodName
- Parameters:
methodName- the Java method name implementing the tool- Returns:
- the tool definition registered under that method's snake-case name, or null when no such tool exists
-
getToolDefinition
- Parameters:
toolName- the MCP tool name- Returns:
- the tool definition registered under that name, or null when no such tool exists
-
isRequireApproval
public boolean isRequireApproval(String methodName, org.springframework.security.core.Authentication authentication) Tells whether a tool call must be confirmed by the end user before it runs. A tool marked as requiring approval only actually asks for one when the caller holds the approval scope.- Parameters:
methodName- the Java method name implementing the toolauthentication- the current OAuth authentication- Returns:
- true when the call must be approved by the user first
-
isAllowedTool
public boolean isAllowedTool(String toolOrMethodName, org.springframework.security.core.Authentication authentication) Same check asisAllowedTool(SimpleToolDefinition, Authentication), for a caller holding only a name: an MCP tool name or the Java method name behind it, tried in that order.- Parameters:
toolOrMethodName- the MCP tool name or its Java method nameauthentication- the current OAuth authentication- Returns:
- true when the caller may use the tool, false when the tool is unknown
-
isAllowedTool
public boolean isAllowedTool(SimpleToolDefinition toolDefinition, org.springframework.security.core.Authentication authentication) Tells whether the authenticated caller may execute, or even see, one given tool: the MCP server must be globally on, and the token must carry a scope matching the tool's read or write nature.The per-user audience is deliberately not asked here. It is enforced at the door —
McpServerOauthOpaqueTokenIntrospectorrefuses an out-of-audience subject — and only there, which is sufficient: one filter chain covers every path under/mcp, every bearer token is introspected on every request (nothing persists an authentication in the HTTP session, and the introspector does not cache), and every MCP verb —initialize,tools/list,tools/call, the SSE stream,DELETE— is a handler that runs after that chain. There is no in-JVM path to a tool callback that skips it, so a second audience check here could only repeat a verdict already given, and asking it per user had made thetools/listcache per user too.A null authentication is refused first: it is not the internal client, and the scope checks below dereference it.
The internal client is exempt from the global flag, as it has always been: EVA calls its tools through the internal client-credentials grant, and that must keep working while MCP is globally off — the flag says no external client may connect, not that the in-product assistant loses its tools. The exemption is recognized by the OAuth client that owns the token (
McpToolUtils.isInternalClientAuthentication(Authentication)) and not by the token subject, which is the user login on a user grant: a user whose login happened to equal the internal client id used to be exempt here, and is not any more.- Parameters:
toolDefinition- the tool being listed or calledauthentication- the current OAuth authentication, may be null- Returns:
- true when the caller may use the tool
-
getToolDefinitions
- Returns:
- every known tool definition by name, importing them from the classpath on first access
-
updateToolDefinition
public org.springframework.ai.tool.definition.ToolDefinition updateToolDefinition(String toolName, String title, String description, String inputSchema, boolean requireApproval, boolean disabled) Updates an administrable tool definition, persists the whole set and notifies listeners so that a running instance picks the change up without a restart.- Parameters:
toolName- the MCP tool nametitle- the new titledescription- the new description shown to the LLMinputSchema- the new JSON input schemarequireApproval- whether calls must be approved by the end userdisabled- whether the tool is withdrawn from the server- Returns:
- the updated tool definition
-
addToolUpdateListener
Registers a listener notified whenever a tool definition changes.- Parameters:
listener- the listener to add
-
isMcpServerEnabled
public boolean isMcpServerEnabled()- Returns:
- true when the MCP server is globally switched on. This is the
instance-wide on/off flag only: it says nothing about whether a
given user belongs to the MCP audience — ask
isMcpServerEnabledForUser(String)for that.
-
isMcpServerEnabledForUser
Answers the per-user MCP question — is MCP globally on and is this user in its audience? — for the places that ask it: the door (McpServerOauthOpaqueTokenIntrospector) and the token endpoint (McpServerOAuthAccessTokenAudienceProvider). Both ask this one method so that they cannot drift apart.Both halves are asked of this service's own collaborators, deliberately not of
ExoFeatureService.isFeatureActiveForUser. That API resolves the audience through whateverFeaturePluginis registered under the feature name, and on a registry missExoFeatureServiceImplfalls back to theexo.feature.mcp.server.permissionsproperty, which is unset on nearly every deployment and then reads as everybody. A miss would open the gate rather than close it, and an enforcement point must not fail open.No
FeaturePluginis registered formcp.serverat all: the one this addon once shipped answered a question nobody asked and was removed. The consequence to know is that a future caller ofExoFeatureService.isFeatureActiveForUser("mcp.server", user)would get that property fallback — everybody on a deployment that never set the property, whatever audience an administrator saved. Nothing calls it today; do not reintroduce it as a shortcut for this method.The global flag is read through
ExoFeatureService.isActiveFeatureand not throughisMcpServerEnabled(), whose node-local memo would make the gate staler than the audience across a cluster. Neither half is memoised: the answer depends on the user and on an audience an administrator may change at any moment, and on a security input a stale wide answer is a hole rather than an inconvenience.- Parameters:
username- the platform login of the end user, may be null or blank- Returns:
- true when that user may use the MCP server; false when the flag is off, when the user is outside the audience, or when no user was given
-
enableMcpServer
public void enableMcpServer()Switches the MCP server on instance-wide and drops the memoised flag so the change is visible on the next request. -
disableMcpServer
public void disableMcpServer()Switches the MCP server off instance-wide and drops the memoised flag so the change is visible on the next request.
-