Class McpToolUtils

java.lang.Object
io.meeds.mcp.server.util.McpToolUtils

public class McpToolUtils extends Object
  • Field Details

    • AI_AGENT_TOOL_EXECUTION_EVENT

      public static final String AI_AGENT_TOOL_EXECUTION_EVENT
      See Also:
    • MCP_OAUTH2_CLIENT_CREDENTIALS_REGISTRATION_ID

      public static final String MCP_OAUTH2_CLIENT_CREDENTIALS_REGISTRATION_ID
      See Also:
    • MCP_SERVER_FEATURE

      public static final String MCP_SERVER_FEATURE
      Name of the ExoFeatureService feature carrying the MCP server's global on/off flag. The per-user audience is not a feature plugin: it is resolved by McpServerAudienceService alone.
      See Also:
    • EVENT_MCP_SERVER_AUDIENCE_UPDATED

      public static final String EVENT_MCP_SERVER_AUDIENCE_UPDATED
      See Also:
    • TOOL_CONTEXT_USER_NAME_PARAM

      public static final String TOOL_CONTEXT_USER_NAME_PARAM
      See Also:
    • TOOL_CONTEXT_ID_PARAM

      public static final String TOOL_CONTEXT_ID_PARAM
      See Also:
    • TOOL_CONTEXT_CONVERSATION_ID_PARAM

      public static final String TOOL_CONTEXT_CONVERSATION_ID_PARAM
      See Also:
    • TOOL_CONTEXT_AGENT_NAME_ID_PARAM

      public static final String TOOL_CONTEXT_AGENT_NAME_ID_PARAM
      Header naming the agent on whose behalf the internal client calls a tool, trusted under the same gate as TOOL_CONTEXT_USER_NAME_PARAM.
      See Also:
    • TOOL_CONTEXT_RETRY_MESSAGE_ID_PARAM

      public static final String TOOL_CONTEXT_RETRY_MESSAGE_ID_PARAM
      Header carried by the internal client when the call belongs to a retried answer, trusted under the same gate as TOOL_CONTEXT_USER_NAME_PARAM.
      See Also:
    • TOOL_CONTEXT_ID

      public static final String TOOL_CONTEXT_ID
    • TOOL_READ_SCOPE

      public static final String TOOL_READ_SCOPE
      See Also:
    • TOOL_WRITE_SCOPE

      public static final String TOOL_WRITE_SCOPE
      See Also:
    • TOOL_WRITE_APPROVE_SCOPE

      public static final String TOOL_WRITE_APPROVE_SCOPE
      See Also:
    • EVENT_TOOL_UPDATED

      public static final String EVENT_TOOL_UPDATED
      See Also:
    • AI_AGENT_TOOL_APPROVED_PARAM

      public static final String AI_AGENT_TOOL_APPROVED_PARAM
      See Also:
    • AI_AGENT_TOOL_USERNAME_PARAM

      public static final String AI_AGENT_TOOL_USERNAME_PARAM
      See Also:
    • AI_AGENT_TOOL_CONVERSATION_ID_PARAM

      public static final String AI_AGENT_TOOL_CONVERSATION_ID_PARAM
      See Also:
    • AI_AGENT_TOOL_INPUT_PARAM

      public static final String AI_AGENT_TOOL_INPUT_PARAM
      See Also:
    • AI_AGENT_TOOL_OUTPUT_PARAM

      public static final String AI_AGENT_TOOL_OUTPUT_PARAM
      See Also:
    • AI_AGENT_TOOL_TYPE_PARAM

      public static final String AI_AGENT_TOOL_TYPE_PARAM
      See Also:
    • AI_AGENT_TOOL_DURATION_PARAM

      public static final String AI_AGENT_TOOL_DURATION_PARAM
      See Also:
    • AI_AGENT_TOOL_START_TIME_PARAM

      public static final String AI_AGENT_TOOL_START_TIME_PARAM
      See Also:
    • AI_AGENT_TOOL_ID_PARAM

      public static final String AI_AGENT_TOOL_ID_PARAM
      See Also:
    • AI_AGENT_TOOL_NAME_PARAM

      public static final String AI_AGENT_TOOL_NAME_PARAM
      See Also:
    • AI_AGENT_TOOL_EXEC_COMPLETED_PARAM

      public static final String AI_AGENT_TOOL_EXEC_COMPLETED_PARAM
      See Also:
    • AI_AGENT_TOOL_GRANT_ID_PARAM

      public static final String AI_AGENT_TOOL_GRANT_ID_PARAM
      Event parameter: the standing approval a call ran under, or created.
      See Also:
    • AI_AGENT_TOOL_GRANT_OWNER_TYPE_PARAM

      public static final String AI_AGENT_TOOL_GRANT_OWNER_TYPE_PARAM
      Event parameter: the owner type of the standing approval.
      See Also:
    • AI_AGENT_TOOL_GRANT_EXPIRES_AT_PARAM

      public static final String AI_AGENT_TOOL_GRANT_EXPIRES_AT_PARAM
      Event parameter: when the created standing approval expires (epoch ms).
      See Also:
    • AI_AGENT_TOOL_GRANTABLE_PARAM

      public static final String AI_AGENT_TOOL_GRANTABLE_PARAM
      Event parameter: whether the approval card may offer "Always allow".
      See Also:
    • AI_AGENT_TOOL_GRANT_MAX_DAYS_PARAM

      public static final String AI_AGENT_TOOL_GRANT_MAX_DAYS_PARAM
      Event parameter: the longest validity, in days, the card may offer.
      See Also:
    • AI_AGENT_TOOL_AGENT_NAME_ID_PARAM

      public static final String AI_AGENT_TOOL_AGENT_NAME_ID_PARAM
      Event parameter: the agent the call is made for, when known.
      See Also:
    • AI_AGENT_TOOL_GRANT_CONSTRAINT_KIND_PARAM

      public static final String AI_AGENT_TOOL_GRANT_CONSTRAINT_KIND_PARAM
      Event parameter: the argument limit kind the card may offer.
      See Also:
    • AI_AGENT_TOOL_GRANT_CONSTRAINT_VALUE_PARAM

      public static final String AI_AGENT_TOOL_GRANT_CONSTRAINT_VALUE_PARAM
      Event parameter: the argument limit value the card may offer.
      See Also:
  • Method Details

    • parseToolDefinitions

      public static List<SimpleToolDefinition> parseToolDefinitions(URL url)
      Parses one ai-tool-definitions.json resource. A tool that declares no icon takes the file's default icon, when the file declares one. A key the file declares at its root and this server does not know is ignored. A tool entry carrying a key this server does not know is left out, with a WARN, and the file's other tools load: loaded, the tool would run with the default of whatever field the key misspells, a misspelled require_approval or annotations key included. The keys inside annotations are read by the MCP SDK's ToolAnnotations, which ignores the ones it does not know.
      Parameters:
      url - the resource to read
      Returns:
      the tool definitions it declares, empty when it cannot be read
    • parseToolDefinitions

      public static List<SimpleToolDefinition> parseToolDefinitions(URL url, Collection<String> leftOutToolNames)
      Same as parseToolDefinitions(URL), and adds the name of each tool left out for a key this server does not know to the given collection.
      Parameters:
      url - the resource to read
      leftOutToolNames - receives the names of the tools left out
      Returns:
      the tool definitions it declares, empty when it cannot be read
    • fromJsonString

      public static final ToolDefinitionMethods fromJsonString(String value)
      Parameters:
      value - the tool definitions as a JSON string
      Returns:
      the parsed definitions, or null when the string is blank
    • fromJsonStringBase64

      public static ToolDefinitionMethods fromJsonStringBase64(String value)
      Same as fromJsonString(String) for the persisted form, whose input schemas are base64 encoded. A saved tool carrying a key this server does not know is kept, with a WARN: the persisted form is written by toJsonStringBase64(ToolDefinitionMethods) alone, so such a key comes from a newer server, not from a misspelling, and dropping the tool would replace the administrator's require_approval and disabled with the shipped values.
      Parameters:
      value - the persisted tool definitions
      Returns:
      the parsed definitions with decoded schemas, or null when blank
    • toJsonStringBase64

      public static String toJsonStringBase64(ToolDefinitionMethods toolDefinitions)
      Serializes tool definitions to their persisted form, base64 encoding each input schema.
      Parameters:
      toolDefinitions - the definitions to serialize
      Returns:
      the JSON string to persist
    • toSnakeCase

      public static String toSnakeCase(String name)
      Parameters:
      name - a Java method name
      Returns:
      its snake-case form, which is the MCP tool name
    • toCamelCase

      public static String toCamelCase(String name)
      Parameters:
      name - a snake-case name
      Returns:
      its camel-case form
    • getMethodToolFieldValue

      public static Object getMethodToolFieldValue(org.springframework.ai.tool.method.MethodToolCallback toolCallback, String fieldName) throws NoSuchFieldException, IllegalAccessException
      Reads a private field of a Spring AI MethodToolCallback, which exposes no accessor for the tool object and method it wraps.
      Parameters:
      toolCallback - the callback to introspect
      fieldName - the field to read
      Returns:
      the field value
      Throws:
      NoSuchFieldException - when the field does not exist
      IllegalAccessException - when it cannot be read
    • getCurrentUserName

      public static String getCurrentUserName()
      Resolves the user on behalf of whom the current Tool is executed: the kernel ConversationState identity when set, else, for the internal client-credentials call only, the TOOL_CONTEXT_USER_NAME_PARAM request header, else the authenticated principal. The header is trusted under the gate of getInternalToolCallRequest() and nowhere else; the internal call is recognized by the OAuth client that owns the token (isInternalClientAuthentication(Authentication)), never by the token subject, so a user whose login happens to be the internal client id is just that user.
      Returns:
      the user name, or null when no user can be resolved
    • getCurrentConversationId

      public static String getCurrentConversationId()
      Resolves the AI chat conversation in which the current Tool is executed. The id travels in the TOOL_CONTEXT_CONVERSATION_ID_PARAM request header set by the internal MCP client and is trusted under exactly the same gate as the user name (getInternalToolCallRequest()): any other caller, an external OAuth MCP client for instance, gets null whatever it sends, so it can never choose the conversation its Tool executions are recorded in.
      Returns:
      the conversation id, or null when the call isn't the internal client's or carries no conversation
    • getCurrentAgentNameId

      public static String getCurrentAgentNameId()
      Resolves the agent on whose behalf the current Tool is executed, from the TOOL_CONTEXT_AGENT_NAME_ID_PARAM header of the internal client only (getInternalToolCallRequest()): any other caller gets null, so an external client can never claim the agent a standing approval names.
      Returns:
      the agent name id, or null when unknown
    • isCurrentCallRetry

      public static boolean isCurrentCallRetry()
      Tells whether the current Tool call belongs to a retried answer, from the TOOL_CONTEXT_RETRY_MESSAGE_ID_PARAM header of the internal client only. The answer can only make the approval gate stricter (no standing approval applies to a retried answer), so no caller gains anything by sending it, and an external client's header is ignored like the others.
      Returns:
      true when the internal client marked the call as a retry
    • getClientId

      public static String getClientId(org.springframework.security.core.Authentication authentication)
      Reads the OAuth client that owns the bearer token of a call.
      Parameters:
      authentication - the current Authentication, may be null
      Returns:
      the client id, or null when the call isn't a bearer-token call
    • isInternalClientAuthentication

      public static boolean isInternalClientAuthentication(org.springframework.security.core.Authentication authentication)
      Tells whether the authentication is the internal client-credentials call: a bearer token whose introspection attributes name MCP_OAUTH2_CLIENT_CREDENTIALS_REGISTRATION_ID as the owning OAuth client (OAuth2TokenIntrospectionClaimNames.CLIENT_ID, set by the authorization server from the registered client the token was issued to). The token subject is deliberately not consulted: it is the client id for a client-credentials grant but the user login for a user grant, so a user whose login is the internal client id would otherwise enter the internal path with an ordinary user token.
      Parameters:
      authentication - the current Authentication, may be null
      Returns:
      true when the token belongs to the internal client, else false
    • isInternalClientPrincipal

      public static boolean isInternalClientPrincipal(Map<String,Object> tokenAttributes)
      Same question as isInternalClientAuthentication(Authentication), asked one step earlier: at token introspection time there is no Authentication yet, only the introspected principal. Both enforcement points of the MCP access gate must recognize the internal client the same way or the exemption means two different things, so both go through isInternalClientId(Object) and neither consults the token subject.
      Parameters:
      tokenAttributes - the introspected token attributes, may be null
      Returns:
      true when the token belongs to the internal client, else false
    • markdownToHtml

      public static String markdownToHtml(String markdown)
      Renders Markdown a tool produced into the HTML the platform's content fields expect, and leaves it untouched when it already carries HTML — a tool's output is not always Markdown, and rendering HTML twice mangles it.
      Parameters:
      markdown - the tool output, may be null or blank
      Returns:
      the rendered HTML, or the input unchanged when it is blank, when it already looks like HTML, or when rendering failed
    • toDate

      public static Date toDate(String date)
      Parameters:
      date - an ISO-8601 date as a tool argument carries it, may be null or blank
      Returns:
      the parsed date, or null when nothing was given
    • formatDate

      public static String formatDate(Long time)
      Parameters:
      time - an epoch timestamp in milliseconds, may be null or non-positive
      Returns:
      the ISO-8601 representation in the current user's time zone, or null when no usable timestamp was given
    • formatDate

      public static String formatDate(Date date)
      Parameters:
      date - the date to render, may be null
      Returns:
      the ISO-8601 representation in the current user's time zone, or null when no date was given
    • getUserTimeZone

      public static TimeZone getUserTimeZone()
      Reads the acting user's time zone from their platform profile, so that a date a tool returns reads the same as the one the web UI shows them.
      Returns:
      the user's time zone, or null when the profile carries none — the caller then falls back to the server's default