From ecff1b965399414ee310bf200a65392b86d6b622 Mon Sep 17 00:00:00 2001 From: Xiaojie Wang Date: Sun, 9 Feb 2025 18:44:15 +0800 Subject: [PATCH 01/51] Add available models for Moonshot Chat Completion Models. --- .../ai/moonshot/api/MoonshotApi.java | 24 +++++++++++++++++++ .../ROOT/pages/api/chat/moonshot-chat.adoc | 6 ++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java b/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java index 532fb851b8b..3d63406841f 100644 --- a/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java +++ b/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java @@ -48,6 +48,7 @@ * * @author Geng Rong * @author Thomas Vitale + * @author Wang Xiaojie */ public class MoonshotApi { @@ -207,14 +208,37 @@ public enum ChatCompletionFinishReason { * Moonshot Chat Completion Models: * * + * + * {@code moonshot-v1-auto} can select the appropriate model based on the number of Tokens occupied by the current context. The available models for selection include: + * + *

{@code moonshot-v1-auto} can be regarded as a model router, which decides which specific model to select based on the number of Tokens occupied by the current context. In terms of performance and output, {@code moonshot-v1-auto} is indistinguishable from the aforementioned models.

+ * The routing rules for the model selected by {@code moonshot-v1-auto} are as follows: + * + * The calculation formula is: + * {@code total_tokens = prompt_tokens + max_tokens} + *

The total number of Tokens is composed of two parts: + *

*/ public enum ChatModel implements ChatModelDescription { // @formatter:off + MOONSHOT_V1_AUTO("moonshot-v1-auto"), MOONSHOT_V1_8K("moonshot-v1-8k"), MOONSHOT_V1_32K("moonshot-v1-32k"), MOONSHOT_V1_128K("moonshot-v1-128k"); diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/moonshot-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/moonshot-chat.adoc index 0bc9717fe4d..c2dae275500 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/moonshot-chat.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/moonshot-chat.adoc @@ -89,7 +89,7 @@ The prefix `spring.ai.moonshot.chat` is the property prefix that lets you config | spring.ai.moonshot.chat.enabled | Enable Moonshot chat model. | true | spring.ai.moonshot.chat.base-url | Optional overrides the spring.ai.moonshot.base-url to provide chat specific url | - | spring.ai.moonshot.chat.api-key | Optional overrides the spring.ai.moonshot.api-key to provide chat specific api-key | - -| spring.ai.moonshot.chat.options.model | This is the Moonshot Chat model to use | `moonshot-v1-8k` (the `moonshot-v1-8k`, `moonshot-v1-32k`, and `moonshot-v1-128k` point to the latest model versions) +| spring.ai.moonshot.chat.options.model | This is the Moonshot Chat model to use | `moonshot-v1-8k` (the `moonshot-v1-auto`, `moonshot-v1-8k`, `moonshot-v1-32k`, and `moonshot-v1-128k` point to the latest model versions) | spring.ai.moonshot.chat.options.maxTokens | The maximum number of tokens to generate in the chat completion. The total length of input tokens and generated tokens is limited by the model's context length. | - | spring.ai.moonshot.chat.options.temperature | The sampling temperature to use that controls the apparent creativity of generated completions. Higher values will make output more random while lower values will make results more focused and deterministic. It is not recommended to modify temperature and top_p for the same completions request as the interaction of these two settings is difficult to predict. | 0.7 | spring.ai.moonshot.chat.options.topP | An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both. | 1.0 @@ -103,6 +103,10 @@ NOTE: You can override the common `spring.ai.moonshot.base-url` and `spring.ai.m The `spring.ai.moonshot.chat.base-url` and `spring.ai.moonshot.chat.api-key` properties if set take precedence over the common properties. This is useful if you want to use different Moonshot accounts for different models and different model endpoints. +NOTE: When the value of `spring.ai.moonshot.chat.options.model` is set to `moonshot-v1-auto`, it can select the appropriate model based on the number of Tokens occupied by the current context. +The available models for selection include: `moonshot-v1-8k`, `moonshot-v1-32k` and `moonshot-v1-128k`. +`moonshot-v1-auto` can be considered as a model router. It decides which specific model to select based on the number of Tokens occupied by the current context. In terms of performance and output, `moonshot-v1-auto` is indistinguishable from the aforementioned models. + TIP: All properties prefixed with `spring.ai.moonshot.chat.options` can be overridden at runtime by adding a request specific <> to the `Prompt` call. == Runtime Options [[chat-options]] From bc3c73cd07a48daf27f485347e890af681f27bfa Mon Sep 17 00:00:00 2001 From: Xiaojie Wang Date: Sun, 9 Feb 2025 19:32:32 +0800 Subject: [PATCH 02/51] Add available models for Moonshot Chat Completion Models. --- .../ai/moonshot/api/MoonshotApi.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java b/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java index 3d63406841f..3b4b9f66f5c 100644 --- a/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java +++ b/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java @@ -214,25 +214,35 @@ public enum ChatCompletionFinishReason { *
  • MOONSHOT_V1_128K - moonshot-v1-128k
  • * * - * {@code moonshot-v1-auto} can select the appropriate model based on the number of Tokens occupied by the current context. The available models for selection include: + * {@code moonshot-v1-auto} can select the appropriate model based on the number of + * Tokens occupied by the current context. The available models for selection include: *
      *
    • {@code moonshot-v1-8k}
    • *
    • {@code moonshot-v1-32k}
    • *
    • {@code moonshot-v1-128k}
    • *
    - *

    {@code moonshot-v1-auto} can be regarded as a model router, which decides which specific model to select based on the number of Tokens occupied by the current context. In terms of performance and output, {@code moonshot-v1-auto} is indistinguishable from the aforementioned models.

    - * The routing rules for the model selected by {@code moonshot-v1-auto} are as follows: + *

    + * {@code moonshot-v1-auto} can be regarded as a model router, which decides which + * specific model to select based on the number of Tokens occupied by the current + * context. In terms of performance and output, {@code moonshot-v1-auto} is + * indistinguishable from the aforementioned models. + *

    + * The routing rules for the model selected by {@code moonshot-v1-auto} are as + * follows: *
      *
    • If {@code total_tokens ≤ 8 * 1024}, choose {@code moonshot-v1-8k}.
    • - *
    • If {@code 8 * 1024 < total_tokens ≤ 32 * 1024}, choose {@code moonshot-v1-32k}.
    • + *
    • If {@code 8 * 1024 < total_tokens ≤ 32 * 1024}, choose + * {@code moonshot-v1-32k}.
    • *
    • If {@code total_tokens > 32 * 1024}, choose {@code moonshot-v1-128k}.
    • *
    - * The calculation formula is: - * {@code total_tokens = prompt_tokens + max_tokens} - *

    The total number of Tokens is composed of two parts: + * The calculation formula is: {@code total_tokens = prompt_tokens + max_tokens} + *

    + * The total number of Tokens is composed of two parts: *

      - *
    • {@code prompt_tokens}: The number of Tokens occupied by the input prompt (Prompt).
    • - *
    • {@code max_tokens}: The maximum number of Tokens expected to be generated as output.
    • + *
    • {@code prompt_tokens}: The number of Tokens occupied by the input prompt + * (Prompt).
    • + *
    • {@code max_tokens}: The maximum number of Tokens expected to be generated as + * output.
    • *
    */ public enum ChatModel implements ChatModelDescription { From e121eb5d2069b21a455822daa7a8e8c7171f2a81 Mon Sep 17 00:00:00 2001 From: Xiaojie Wang Date: Sun, 9 Feb 2025 18:44:15 +0800 Subject: [PATCH 03/51] Add available models for Moonshot Chat Completion Models. Signed-off-by: Xiaojie Wang --- .../ai/moonshot/api/MoonshotApi.java | 24 +++++++++++++++++++ .../ROOT/pages/api/chat/moonshot-chat.adoc | 6 ++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java b/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java index 532fb851b8b..3d63406841f 100644 --- a/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java +++ b/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java @@ -48,6 +48,7 @@ * * @author Geng Rong * @author Thomas Vitale + * @author Wang Xiaojie */ public class MoonshotApi { @@ -207,14 +208,37 @@ public enum ChatCompletionFinishReason { * Moonshot Chat Completion Models: * *
      + *
    • MOONSHOT_V1_AUTO - moonshot-v1-auto
    • *
    • MOONSHOT_V1_8K - moonshot-v1-8k
    • *
    • MOONSHOT_V1_32K - moonshot-v1-32k
    • *
    • MOONSHOT_V1_128K - moonshot-v1-128k
    • *
    + * + * {@code moonshot-v1-auto} can select the appropriate model based on the number of Tokens occupied by the current context. The available models for selection include: + *
      + *
    • {@code moonshot-v1-8k}
    • + *
    • {@code moonshot-v1-32k}
    • + *
    • {@code moonshot-v1-128k}
    • + *
    + *

    {@code moonshot-v1-auto} can be regarded as a model router, which decides which specific model to select based on the number of Tokens occupied by the current context. In terms of performance and output, {@code moonshot-v1-auto} is indistinguishable from the aforementioned models.

    + * The routing rules for the model selected by {@code moonshot-v1-auto} are as follows: + *
      + *
    • If {@code total_tokens ≤ 8 * 1024}, choose {@code moonshot-v1-8k}.
    • + *
    • If {@code 8 * 1024 < total_tokens ≤ 32 * 1024}, choose {@code moonshot-v1-32k}.
    • + *
    • If {@code total_tokens > 32 * 1024}, choose {@code moonshot-v1-128k}.
    • + *
    + * The calculation formula is: + * {@code total_tokens = prompt_tokens + max_tokens} + *

    The total number of Tokens is composed of two parts: + *

      + *
    • {@code prompt_tokens}: The number of Tokens occupied by the input prompt (Prompt).
    • + *
    • {@code max_tokens}: The maximum number of Tokens expected to be generated as output.
    • + *
    */ public enum ChatModel implements ChatModelDescription { // @formatter:off + MOONSHOT_V1_AUTO("moonshot-v1-auto"), MOONSHOT_V1_8K("moonshot-v1-8k"), MOONSHOT_V1_32K("moonshot-v1-32k"), MOONSHOT_V1_128K("moonshot-v1-128k"); diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/moonshot-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/moonshot-chat.adoc index 0bc9717fe4d..c2dae275500 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/moonshot-chat.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/moonshot-chat.adoc @@ -89,7 +89,7 @@ The prefix `spring.ai.moonshot.chat` is the property prefix that lets you config | spring.ai.moonshot.chat.enabled | Enable Moonshot chat model. | true | spring.ai.moonshot.chat.base-url | Optional overrides the spring.ai.moonshot.base-url to provide chat specific url | - | spring.ai.moonshot.chat.api-key | Optional overrides the spring.ai.moonshot.api-key to provide chat specific api-key | - -| spring.ai.moonshot.chat.options.model | This is the Moonshot Chat model to use | `moonshot-v1-8k` (the `moonshot-v1-8k`, `moonshot-v1-32k`, and `moonshot-v1-128k` point to the latest model versions) +| spring.ai.moonshot.chat.options.model | This is the Moonshot Chat model to use | `moonshot-v1-8k` (the `moonshot-v1-auto`, `moonshot-v1-8k`, `moonshot-v1-32k`, and `moonshot-v1-128k` point to the latest model versions) | spring.ai.moonshot.chat.options.maxTokens | The maximum number of tokens to generate in the chat completion. The total length of input tokens and generated tokens is limited by the model's context length. | - | spring.ai.moonshot.chat.options.temperature | The sampling temperature to use that controls the apparent creativity of generated completions. Higher values will make output more random while lower values will make results more focused and deterministic. It is not recommended to modify temperature and top_p for the same completions request as the interaction of these two settings is difficult to predict. | 0.7 | spring.ai.moonshot.chat.options.topP | An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both. | 1.0 @@ -103,6 +103,10 @@ NOTE: You can override the common `spring.ai.moonshot.base-url` and `spring.ai.m The `spring.ai.moonshot.chat.base-url` and `spring.ai.moonshot.chat.api-key` properties if set take precedence over the common properties. This is useful if you want to use different Moonshot accounts for different models and different model endpoints. +NOTE: When the value of `spring.ai.moonshot.chat.options.model` is set to `moonshot-v1-auto`, it can select the appropriate model based on the number of Tokens occupied by the current context. +The available models for selection include: `moonshot-v1-8k`, `moonshot-v1-32k` and `moonshot-v1-128k`. +`moonshot-v1-auto` can be considered as a model router. It decides which specific model to select based on the number of Tokens occupied by the current context. In terms of performance and output, `moonshot-v1-auto` is indistinguishable from the aforementioned models. + TIP: All properties prefixed with `spring.ai.moonshot.chat.options` can be overridden at runtime by adding a request specific <> to the `Prompt` call. == Runtime Options [[chat-options]] From a40d2924726575c07cfd30df6e2e3f8153aaf451 Mon Sep 17 00:00:00 2001 From: Xiaojie Wang Date: Sun, 9 Feb 2025 19:32:32 +0800 Subject: [PATCH 04/51] Add available models for Moonshot Chat Completion Models. Signed-off-by: Xiaojie Wang --- .../ai/moonshot/api/MoonshotApi.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java b/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java index 3d63406841f..3b4b9f66f5c 100644 --- a/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java +++ b/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java @@ -214,25 +214,35 @@ public enum ChatCompletionFinishReason { *
  • MOONSHOT_V1_128K - moonshot-v1-128k
  • * * - * {@code moonshot-v1-auto} can select the appropriate model based on the number of Tokens occupied by the current context. The available models for selection include: + * {@code moonshot-v1-auto} can select the appropriate model based on the number of + * Tokens occupied by the current context. The available models for selection include: *
      *
    • {@code moonshot-v1-8k}
    • *
    • {@code moonshot-v1-32k}
    • *
    • {@code moonshot-v1-128k}
    • *
    - *

    {@code moonshot-v1-auto} can be regarded as a model router, which decides which specific model to select based on the number of Tokens occupied by the current context. In terms of performance and output, {@code moonshot-v1-auto} is indistinguishable from the aforementioned models.

    - * The routing rules for the model selected by {@code moonshot-v1-auto} are as follows: + *

    + * {@code moonshot-v1-auto} can be regarded as a model router, which decides which + * specific model to select based on the number of Tokens occupied by the current + * context. In terms of performance and output, {@code moonshot-v1-auto} is + * indistinguishable from the aforementioned models. + *

    + * The routing rules for the model selected by {@code moonshot-v1-auto} are as + * follows: *
      *
    • If {@code total_tokens ≤ 8 * 1024}, choose {@code moonshot-v1-8k}.
    • - *
    • If {@code 8 * 1024 < total_tokens ≤ 32 * 1024}, choose {@code moonshot-v1-32k}.
    • + *
    • If {@code 8 * 1024 < total_tokens ≤ 32 * 1024}, choose + * {@code moonshot-v1-32k}.
    • *
    • If {@code total_tokens > 32 * 1024}, choose {@code moonshot-v1-128k}.
    • *
    - * The calculation formula is: - * {@code total_tokens = prompt_tokens + max_tokens} - *

    The total number of Tokens is composed of two parts: + * The calculation formula is: {@code total_tokens = prompt_tokens + max_tokens} + *

    + * The total number of Tokens is composed of two parts: *

      - *
    • {@code prompt_tokens}: The number of Tokens occupied by the input prompt (Prompt).
    • - *
    • {@code max_tokens}: The maximum number of Tokens expected to be generated as output.
    • + *
    • {@code prompt_tokens}: The number of Tokens occupied by the input prompt + * (Prompt).
    • + *
    • {@code max_tokens}: The maximum number of Tokens expected to be generated as + * output.
    • *
    */ public enum ChatModel implements ChatModelDescription { From 1bd53eb307ae05483d9cdd388579fdcac9a2b48d Mon Sep 17 00:00:00 2001 From: Jinho Lee <52993842+birariro@users.noreply.github.com> Date: Tue, 11 Feb 2025 05:09:31 +0900 Subject: [PATCH 05/51] fix sample code in perplexity-chat.adoc (#2205) Signed-off-by: birariro --- .../modules/ROOT/pages/api/chat/perplexity-chat.adoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/perplexity-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/perplexity-chat.adoc index 0ab79504722..e8087ab6833 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/perplexity-chat.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/perplexity-chat.adoc @@ -141,13 +141,14 @@ ChatResponse response = chatModel.call( new Prompt( "Generate the names of 5 famous pirates.", OpenAiChatOptions.builder() - .withModel("llama-3.1-sonar-large-128k-online") - .withTemperature(0.4) + .model("llama-3.1-sonar-large-128k-online") + .temperature(0.4) .build() )); ---- -TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java[OpenAiChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java[ChatOptionsBuilder#builder()]. +TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java[OpenAiChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java#L97[ChatOptions#builder()]. + == Function Calling From 7fb752e4c1fc11da7812eb2d4ea2a08358e22157 Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Fri, 7 Feb 2025 14:57:18 +0100 Subject: [PATCH 06/51] Refactor: MCP Autoconfig Modularization Core Architecture Changes: - Split MCP into dedicated client/server modules - Created separate starters: spring-ai-starter-mcp-webmvc and spring-ai-starter-mcp-webflux - Removed property-based transport configuration in favor of auto-configuration - Added support for multiple transport types (STDIO, WebMVC, WebFlux) Client Improvements: - Added support for both synchronous and asynchronous MCP clients - Fixed client auto-configuration issues - Added root change notification property to common properties Configuration Enhancements: - Improved configuration properties organization and validation - Added ConditionalOnMissingBean for WebMvc/WebFlux configurations - Enhanced lifecycle management and customization support Testing and Documentation: - Added comprehensive integration tests for McpClientAutoConfiguration - Updated McpServerAutoConfigurationIT - Added extensive JavaDoc documentation - Improved MCP client/server starter documentation - Added documentation for common utilities - Updated navigation for new MCP documentation sections Signed-off-by: Christian Tzolov --- .../spring-ai-mcp-client/pom.xml | 74 ++++ .../client/McpClientAutoConfiguration.java | 293 +++++++++++++++ .../mcp/client/NamedClientMcpTransport.java | 32 ++ ...eHttpClientTransportAutoConfiguration.java | 116 ++++++ .../SseWebFluxTransportAutoConfiguration.java | 124 ++++++ .../StdioTransportAutoConfiguration.java | 87 +++++ .../configurer/McpAsyncClientConfigurer.java | 16 +- .../configurer/McpSyncClientConfigurer.java | 76 ++++ .../properties/McpClientCommonProperties.java | 160 ++++++++ .../properties/McpSseClientProperties.java | 73 ++++ .../properties}/McpStdioClientProperties.java | 91 +---- ...ot.autoconfigure.AutoConfiguration.imports | 21 ++ .../client/McpClientAutoConfigurationIT.java | 190 ++++++++++ .../resources/application-test.properties | 10 + .../spring-ai-mcp-server/pom.xml | 62 +++ .../mcp/server/McpServerProperties.java | 65 +--- .../server/MpcServerAutoConfiguration.java | 93 +++-- .../MpcWebFluxServerAutoConfiguration.java | 5 +- .../MpcWebMvcServerAutoConfiguration.java | 9 +- ...ot.autoconfigure.AutoConfiguration.imports | 20 + .../server/McpServerAutoConfigurationIT.java | 311 +++++++++++++++ mcp-client-boot-starter-docs.adoc | 340 +++++++++++++++++ mcp-client-boot-starter-docs.md | 284 ++++++++++++++ mcp-helpers.adoc | 168 +++++++++ mcp-helpers.md | 156 ++++++++ mcp-server-boot-starter-docs.adoc | 310 +++++++++++++++ mcp-server-boot-starter-docs.md | 294 +++++++++++++++ .../ai/mcp/AsyncMcpToolCallback.java | 113 ++++++ .../ai/mcp/AsyncMcpToolCallbackProvider.java | 132 +++++++ .../ai/mcp/McpSyncClientCustomizer.java | 28 -- .../springframework/ai/mcp/McpToolUtils.java | 64 +++- ...Callback.java => SyncMcpToolCallback.java} | 7 +- .../ai/mcp/SyncMcpToolCallbackProvider.java | 24 +- .../ai/mcp/{ => aot}/McpHints.java | 46 ++- .../customizer/McpAsyncClientCustomizer.java | 44 +++ .../customizer/McpSyncClientCustomizer.java | 44 +++ .../springframework/ai/mcp/package-info.java | 20 + .../resources/META-INF/spring/aot.factories | 2 +- ... => SyncMcpToolCallbackProviderTests.java} | 2 +- ...sts.java => SyncMcpToolCallbackTests.java} | 6 +- pom.xml | 21 +- spring-ai-bom/pom.xml | 39 +- .../mcp/java-mcp-client-architecture.jpg | Bin 0 -> 676694 bytes .../mcp/java-mcp-server-architecture.jpg | Bin 0 -> 859935 bytes .../images/mcp/java-mcp-uml-classdiagram.svg | 1 + .../modules/ROOT/images/mcp/mcp-stack.svg | 197 ++++++++++ .../src/main/antora/modules/ROOT/nav.adoc | 6 +- .../api/mcp/mcp-client-boot-starter-docs.adoc | 353 ++++++++++++++++++ .../ROOT/pages/api/mcp/mcp-helpers.adoc | 156 ++++++++ .../ROOT/pages/api/mcp/mcp-overview.adoc | 90 +++++ .../api/mcp/mcp-server-boot-starter-docs.adoc | 218 +++++++++++ .../pages/api/model-context-protocol.adoc | 117 ------ spring-ai-spring-boot-autoconfigure/pom.xml | 3 - .../mcp/client/stdio/McpStdioConnection.java | 61 --- .../MpcStdioClientAutoConfiguration.java | 137 ------- ...ot.autoconfigure.AutoConfiguration.imports | 5 - .../server/McpServerAutoConfigurationIT.java | 93 ----- .../pom.xml | 66 ++++ .../pom.xml | 8 +- .../pom.xml | 72 ++++ .../pom.xml | 71 ++++ .../spring-ai-starter-mcp-server/pom.xml | 58 +++ 62 files changed, 5129 insertions(+), 655 deletions(-) create mode 100644 auto-configurations/spring-ai-mcp-client/pom.xml create mode 100644 auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/McpClientAutoConfiguration.java create mode 100644 auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/NamedClientMcpTransport.java create mode 100644 auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseHttpClientTransportAutoConfiguration.java create mode 100644 auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseWebFluxTransportAutoConfiguration.java create mode 100644 auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/StdioTransportAutoConfiguration.java rename spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/client/stdio/McpSyncClientConfigurer.java => auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/configurer/McpAsyncClientConfigurer.java (62%) create mode 100644 auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/configurer/McpSyncClientConfigurer.java create mode 100644 auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpClientCommonProperties.java create mode 100644 auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpSseClientProperties.java rename {spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/client/stdio => auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties}/McpStdioClientProperties.java (66%) create mode 100644 auto-configurations/spring-ai-mcp-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 auto-configurations/spring-ai-mcp-client/src/test/java/org/springframework/ai/autoconfigure/mcp/client/McpClientAutoConfigurationIT.java create mode 100644 auto-configurations/spring-ai-mcp-client/src/test/resources/application-test.properties create mode 100644 auto-configurations/spring-ai-mcp-server/pom.xml rename {spring-ai-spring-boot-autoconfigure => auto-configurations/spring-ai-mcp-server}/src/main/java/org/springframework/ai/autoconfigure/mcp/server/McpServerProperties.java (82%) rename {spring-ai-spring-boot-autoconfigure => auto-configurations/spring-ai-mcp-server}/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcServerAutoConfiguration.java (72%) rename {spring-ai-spring-boot-autoconfigure => auto-configurations/spring-ai-mcp-server}/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebFluxServerAutoConfiguration.java (94%) rename {spring-ai-spring-boot-autoconfigure => auto-configurations/spring-ai-mcp-server}/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebMvcServerAutoConfiguration.java (94%) create mode 100644 auto-configurations/spring-ai-mcp-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 auto-configurations/spring-ai-mcp-server/src/test/java/org/springframework/ai/autoconfigure/mcp/server/McpServerAutoConfigurationIT.java create mode 100644 mcp-client-boot-starter-docs.adoc create mode 100644 mcp-client-boot-starter-docs.md create mode 100644 mcp-helpers.adoc create mode 100644 mcp-helpers.md create mode 100644 mcp-server-boot-starter-docs.adoc create mode 100644 mcp-server-boot-starter-docs.md create mode 100644 mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java create mode 100644 mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallbackProvider.java delete mode 100644 mcp/common/src/main/java/org/springframework/ai/mcp/McpSyncClientCustomizer.java rename mcp/common/src/main/java/org/springframework/ai/mcp/{McpToolCallback.java => SyncMcpToolCallback.java} (95%) rename mcp/common/src/main/java/org/springframework/ai/mcp/{ => aot}/McpHints.java (54%) create mode 100644 mcp/common/src/main/java/org/springframework/ai/mcp/customizer/McpAsyncClientCustomizer.java create mode 100644 mcp/common/src/main/java/org/springframework/ai/mcp/customizer/McpSyncClientCustomizer.java rename mcp/common/src/test/java/org/springframework/ai/mcp/{McpToolCallbackProviderTests.java => SyncMcpToolCallbackProviderTests.java} (98%) rename mcp/common/src/test/java/org/springframework/ai/mcp/{McpToolCallbackTests.java => SyncMcpToolCallbackTests.java} (92%) create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/images/mcp/java-mcp-client-architecture.jpg create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/images/mcp/java-mcp-server-architecture.jpg create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/images/mcp/java-mcp-uml-classdiagram.svg create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/images/mcp/mcp-stack.svg create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-client-boot-starter-docs.adoc create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-helpers.adoc create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-overview.adoc create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc delete mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/pages/api/model-context-protocol.adoc delete mode 100644 spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/client/stdio/McpStdioConnection.java delete mode 100644 spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/client/stdio/MpcStdioClientAutoConfiguration.java delete mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/mcp/server/McpServerAutoConfigurationIT.java create mode 100644 spring-ai-spring-boot-starters/spring-ai-starter-mcp-client-webflux/pom.xml rename spring-ai-spring-boot-starters/{spring-ai-starter-mcp => spring-ai-starter-mcp-client}/pom.xml (88%) create mode 100644 spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webflux/pom.xml create mode 100644 spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webmvc/pom.xml create mode 100644 spring-ai-spring-boot-starters/spring-ai-starter-mcp-server/pom.xml diff --git a/auto-configurations/spring-ai-mcp-client/pom.xml b/auto-configurations/spring-ai-mcp-client/pom.xml new file mode 100644 index 00000000000..da1b08b6784 --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + org.springframework.ai + spring-ai + 1.0.0-SNAPSHOT + ../../pom.xml + + spring-ai-mcp-client-spring-boot-autoconfigure + jar + Spring AI MCP Client Auto Configuration + Spring AI MCP Client Auto Configuration + https://github.com/spring-projects/spring-ai + + + https://github.com/spring-projects/spring-ai + git://github.com/spring-projects/spring-ai.git + git@github.com:spring-projects/spring-ai.git + + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.ai + spring-ai-mcp + ${project.parent.version} + true + + + + io.modelcontextprotocol.sdk + mcp-spring-webflux + true + + + + + + + + org.springframework.ai + spring-ai-test + ${project.parent.version} + test + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.mockito + mockito-core + test + + + + diff --git a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/McpClientAutoConfiguration.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/McpClientAutoConfiguration.java new file mode 100644 index 00000000000..23a3d10130c --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/McpClientAutoConfiguration.java @@ -0,0 +1,293 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.autoconfigure.mcp.client; + +import java.util.ArrayList; +import java.util.List; + +import io.modelcontextprotocol.client.McpAsyncClient; +import io.modelcontextprotocol.client.McpClient; +import io.modelcontextprotocol.client.McpSyncClient; +import io.modelcontextprotocol.spec.McpSchema; + +import org.springframework.ai.autoconfigure.mcp.client.configurer.McpAsyncClientConfigurer; +import org.springframework.ai.autoconfigure.mcp.client.configurer.McpSyncClientConfigurer; +import org.springframework.ai.autoconfigure.mcp.client.properties.McpClientCommonProperties; +import org.springframework.ai.mcp.McpToolUtils; +import org.springframework.ai.mcp.customizer.McpAsyncClientCustomizer; +import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.util.CollectionUtils; + +/** + * Auto-configuration for Model Context Protocol (MCP) client support. + * + *

    + * This configuration class sets up the necessary beans for MCP client functionality, + * including both synchronous and asynchronous clients along with their respective tool + * callbacks. It is automatically enabled when the required classes are present on the + * classpath and can be explicitly disabled through properties. + * + *

    + * Configuration Properties: + *

      + *
    • {@code spring.ai.mcp.client.enabled} - Enable/disable MCP client support (default: + * true) + *
    • {@code spring.ai.mcp.client.type} - Client type: SYNC or ASYNC (default: SYNC) + *
    • {@code spring.ai.mcp.client.name} - Client implementation name + *
    • {@code spring.ai.mcp.client.version} - Client implementation version + *
    • {@code spring.ai.mcp.client.request-timeout} - Request timeout duration + *
    • {@code spring.ai.mcp.client.initialized} - Whether to initialize clients on + * creation + *
    + * + *

    + * The configuration is activated after the transport-specific auto-configurations (Stdio, + * SSE HTTP, and SSE WebFlux) to ensure proper initialization order. At least one + * transport must be available for the clients to be created. + * + *

    + * Key features: + *

      + *
    • Synchronous and Asynchronous Client Support: + *
        + *
      • Creates and configures MCP clients based on available transports + *
      • Supports both blocking (sync) and non-blocking (async) operations + *
      • Automatic client initialization if enabled + *
      + *
    • Integration Support: + *
        + *
      • Sets up tool callbacks for Spring AI integration + *
      • Supports multiple named transports + *
      • Proper lifecycle management with automatic cleanup + *
      + *
    • Customization Options: + *
        + *
      • Extensible through {@link McpSyncClientCustomizer} and + * {@link McpAsyncClientCustomizer} + *
      • Configurable timeouts and client information + *
      • Support for custom transport implementations + *
      + *
    + * + * @see McpSyncClient + * @see McpAsyncClient + * @see McpClientCommonProperties + * @see McpSyncClientCustomizer + * @see McpAsyncClientCustomizer + * @see StdioTransportAutoConfiguration + * @see SseHttpClientTransportAutoConfiguration + * @see SseWebFluxTransportAutoConfiguration + */ +@AutoConfiguration(after = { StdioTransportAutoConfiguration.class, SseHttpClientTransportAutoConfiguration.class, + SseWebFluxTransportAutoConfiguration.class }) +@ConditionalOnClass({ McpSchema.class }) +@EnableConfigurationProperties(McpClientCommonProperties.class) +@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", + matchIfMissing = true) +public class McpClientAutoConfiguration { + + /** + * Creates a list of {@link McpSyncClient} instances based on the available + * transports. + * + *

    + * Each client is configured with: + *

      + *
    • Client information (name and version) from common properties + *
    • Request timeout settings + *
    • Custom configurations through {@link McpSyncClientConfigurer} + *
    + * + *

    + * If initialization is enabled in properties, the clients are automatically + * initialized. + * @param mcpSyncClientConfigurer the configurer for customizing client creation + * @param commonProperties common MCP client properties + * @param transportsProvider provider of named MCP transports + * @return list of configured MCP sync clients + */ + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", + matchIfMissing = true) + public List mcpSyncClients(McpSyncClientConfigurer mcpSyncClientConfigurer, + McpClientCommonProperties commonProperties, + ObjectProvider> transportsProvider) { + + List mcpSyncClients = new ArrayList<>(); + + List namedTransports = transportsProvider.stream().flatMap(List::stream).toList(); + + if (!CollectionUtils.isEmpty(namedTransports)) { + for (NamedClientMcpTransport namedTransport : namedTransports) { + + McpSchema.Implementation clientInfo = new McpSchema.Implementation(commonProperties.getName(), + commonProperties.getVersion()); + + McpClient.SyncSpec syncSpec = McpClient.sync(namedTransport.transport()) + .clientInfo(clientInfo) + .requestTimeout(commonProperties.getRequestTimeout()); + + syncSpec = mcpSyncClientConfigurer.configure(namedTransport.name(), syncSpec); + + var syncClient = syncSpec.build(); + + if (commonProperties.isInitialized()) { + syncClient.initialize(); + } + + mcpSyncClients.add(syncClient); + } + } + + return mcpSyncClients; + } + + /** + * Creates tool callbacks for all configured MCP clients. + * + *

    + * These callbacks enable integration with Spring AI's tool execution framework, + * allowing MCP tools to be used as part of AI interactions. + * @param mcpClientsProvider provider of MCP sync clients + * @return list of tool callbacks for MCP integration + */ + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", + matchIfMissing = true) + public List toolCallbacks(ObjectProvider> mcpClientsProvider) { + List mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList(); + return McpToolUtils.getToolCallbacksFromSyncClients(mcpClients); + } + + /** + * Record class that implements {@link AutoCloseable} to ensure proper cleanup of MCP + * clients. + * + *

    + * This class is responsible for closing all MCP sync clients when the application + * context is closed, preventing resource leaks. + */ + public record ClosebleMcpSyncClients(List clients) implements AutoCloseable { + + @Override + public void close() { + this.clients.forEach(McpSyncClient::close); + } + } + + /** + * Creates a closeable wrapper for MCP sync clients to ensure proper resource cleanup. + * @param clients the list of MCP sync clients to manage + * @return a closeable wrapper for the clients + */ + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", + matchIfMissing = true) + public ClosebleMcpSyncClients makeSyncClientsClosable(List clients) { + return new ClosebleMcpSyncClients(clients); + } + + /** + * Creates the default {@link McpSyncClientConfigurer} if none is provided. + * + *

    + * This configurer aggregates all available {@link McpSyncClientCustomizer} instances + * to allow for customization of MCP sync client creation. + * @param customizerProvider provider of MCP sync client customizers + * @return the configured MCP sync client configurer + */ + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", + matchIfMissing = true) + McpSyncClientConfigurer mcpSyncClientConfigurer(ObjectProvider customizerProvider) { + return new McpSyncClientConfigurer(customizerProvider.orderedStream().toList()); + } + + // Async client configuration + + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") + public List mcpAsyncClients(McpAsyncClientConfigurer mcpSyncClientConfigurer, + McpClientCommonProperties commonProperties, + ObjectProvider> transportsProvider) { + + List mcpSyncClients = new ArrayList<>(); + + List namedTransports = transportsProvider.stream().flatMap(List::stream).toList(); + + if (!CollectionUtils.isEmpty(namedTransports)) { + for (NamedClientMcpTransport namedTransport : namedTransports) { + + McpSchema.Implementation clientInfo = new McpSchema.Implementation(commonProperties.getName(), + commonProperties.getVersion()); + + McpClient.AsyncSpec syncSpec = McpClient.async(namedTransport.transport()) + .clientInfo(clientInfo) + .requestTimeout(commonProperties.getRequestTimeout()); + + syncSpec = mcpSyncClientConfigurer.configure(namedTransport.name(), syncSpec); + + var syncClient = syncSpec.build(); + + if (commonProperties.isInitialized()) { + syncClient.initialize(); + } + + mcpSyncClients.add(syncClient); + } + } + + return mcpSyncClients; + } + + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") + public List asyncToolCallbacks(ObjectProvider> mcpClientsProvider) { + List mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList(); + return McpToolUtils.getToolCallbacksFromAsyncClinents(mcpClients); + } + + public record ClosebleMcpAsyncClients(List clients) implements AutoCloseable { + @Override + public void close() { + this.clients.forEach(McpAsyncClient::close); + } + } + + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") + public ClosebleMcpAsyncClients makeAsynClientsClosable(List clients) { + return new ClosebleMcpAsyncClients(clients); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") + McpAsyncClientConfigurer mcpAsyncClientConfigurer(ObjectProvider customizerProvider) { + return new McpAsyncClientConfigurer(customizerProvider.orderedStream().toList()); + } + +} diff --git a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/NamedClientMcpTransport.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/NamedClientMcpTransport.java new file mode 100644 index 00000000000..de2bb7c60c5 --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/NamedClientMcpTransport.java @@ -0,0 +1,32 @@ +/* +* Copyright 2024 - 2024 the original author or authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.springframework.ai.autoconfigure.mcp.client; + +import io.modelcontextprotocol.spec.ClientMcpTransport; + +/** + * A named MCP client transport. Usually created by the transport auto-configurations, but + * you can also create them manually. Expose the list castom NamedClientMcpTransport + * as @Bean. + * + * @param name the name of the transport. Usually the name of the server connection. + * @param transport the MCP client transport. + * @author Christian Tzolov + * @since 1.0.0 + */ +public record NamedClientMcpTransport(String name, ClientMcpTransport transport) { + +} diff --git a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseHttpClientTransportAutoConfiguration.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseHttpClientTransportAutoConfiguration.java new file mode 100644 index 00000000000..31aaa2244b1 --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseHttpClientTransportAutoConfiguration.java @@ -0,0 +1,116 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.autoconfigure.mcp.client; + +import java.net.http.HttpClient; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.client.McpSyncClient; +import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; +import io.modelcontextprotocol.spec.McpSchema; + +import org.springframework.ai.autoconfigure.mcp.client.properties.McpClientCommonProperties; +import org.springframework.ai.autoconfigure.mcp.client.properties.McpSseClientProperties; +import org.springframework.ai.autoconfigure.mcp.client.properties.McpSseClientProperties.SseParameters; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * Auto-configuration for Server-Sent Events (SSE) HTTP client transport in the Model + * Context Protocol (MCP). + * + *

    + * This configuration class sets up the necessary beans for SSE-based HTTP client + * transport when WebFlux is not available. It provides HTTP client-based SSE transport + * implementation for MCP client communication. + * + *

    + * The configuration is activated after the WebFlux SSE transport auto-configuration to + * ensure proper fallback behavior when WebFlux is not available. + * + *

    + * Key features: + *

      + *
    • Creates HTTP client-based SSE transports for configured MCP server connections + *
    • Configures ObjectMapper for JSON serialization/deserialization + *
    • Supports multiple named server connections with different URLs + *
    + * + * @see HttpClientSseClientTransport + * @see McpSseClientProperties + */ +@AutoConfiguration(after = SseWebFluxTransportAutoConfiguration.class) +@ConditionalOnClass({ McpSchema.class, McpSyncClient.class }) +@ConditionalOnMissingClass("io.modelcontextprotocol.client.transport.public class WebFluxSseClientTransport") +@EnableConfigurationProperties({ McpSseClientProperties.class, McpClientCommonProperties.class }) +@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", + matchIfMissing = true) +public class SseHttpClientTransportAutoConfiguration { + + /** + * Creates a list of HTTP client-based SSE transports for MCP communication. + * + *

    + * Each transport is configured with: + *

      + *
    • A new HttpClient instance + *
    • Server URL from properties + *
    • ObjectMapper for JSON processing + *
    + * @param sseProperties the SSE client properties containing server configurations + * @param objectMapper the ObjectMapper for JSON serialization/deserialization + * @return list of named MCP transports + */ + @Bean + public List mcpHttpClientTransports(McpSseClientProperties sseProperties, + ObjectMapper objectMapper) { + + List sseTransports = new ArrayList<>(); + + for (Map.Entry serverParameters : sseProperties.getConnections().entrySet()) { + + var transport = new HttpClientSseClientTransport(HttpClient.newBuilder(), serverParameters.getValue().url(), + objectMapper); + sseTransports.add(new NamedClientMcpTransport(serverParameters.getKey(), transport)); + } + + return sseTransports; + } + + /** + * Creates the default ObjectMapper if none is provided. + * + *

    + * This ObjectMapper is used for JSON serialization and deserialization in the SSE + * transport implementation. + * @return the configured ObjectMapper instance + */ + @Bean + @ConditionalOnMissingBean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + +} diff --git a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseWebFluxTransportAutoConfiguration.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseWebFluxTransportAutoConfiguration.java new file mode 100644 index 00000000000..d92520e5a67 --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseWebFluxTransportAutoConfiguration.java @@ -0,0 +1,124 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.autoconfigure.mcp.client; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport; + +import org.springframework.ai.autoconfigure.mcp.client.properties.McpClientCommonProperties; +import org.springframework.ai.autoconfigure.mcp.client.properties.McpSseClientProperties; +import org.springframework.ai.autoconfigure.mcp.client.properties.McpSseClientProperties.SseParameters; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Auto-configuration for WebFlux-based Server-Sent Events (SSE) client transport in the + * Model Context Protocol (MCP). + * + *

    + * This configuration class sets up the necessary beans for SSE-based WebFlux transport, + * providing reactive transport implementation for MCP client communication when WebFlux + * is available on the classpath. + * + *

    + * Key features: + *

      + *
    • Creates WebFlux-based SSE transports for configured MCP server connections + *
    • Configures WebClient.Builder for HTTP client operations + *
    • Sets up ObjectMapper for JSON serialization/deserialization + *
    • Supports multiple named server connections with different base URLs + *
    + * + * @see WebFluxSseClientTransport + * @see McpSseClientProperties + */ +@AutoConfiguration +@ConditionalOnClass(WebFluxSseClientTransport.class) +@EnableConfigurationProperties({ McpSseClientProperties.class, McpClientCommonProperties.class }) +@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", + matchIfMissing = true) +public class SseWebFluxTransportAutoConfiguration { + + /** + * Creates a list of WebFlux-based SSE transports for MCP communication. + * + *

    + * Each transport is configured with: + *

      + *
    • A cloned WebClient.Builder with server-specific base URL + *
    • ObjectMapper for JSON processing + *
    • Server connection parameters from properties + *
    + * @param sseProperties the SSE client properties containing server configurations + * @param webClientBuilderTemplate the template WebClient.Builder to clone for each + * connection + * @param objectMapper the ObjectMapper for JSON serialization/deserialization + * @return list of named MCP transports + */ + @Bean + public List webFluxClientTransports(McpSseClientProperties sseProperties, + WebClient.Builder webClientBuilderTemplate, ObjectMapper objectMapper) { + + List sseTransports = new ArrayList<>(); + + for (Map.Entry serverParameters : sseProperties.getConnections().entrySet()) { + var webClientBuilder = webClientBuilderTemplate.clone().baseUrl(serverParameters.getValue().url()); + var transport = new WebFluxSseClientTransport(webClientBuilder, objectMapper); + sseTransports.add(new NamedClientMcpTransport(serverParameters.getKey(), transport)); + } + + return sseTransports; + } + + /** + * Creates the default WebClient.Builder if none is provided. + * + *

    + * This builder serves as a template for creating server-specific WebClient instances + * used in SSE transport implementation. + * @return the configured WebClient.Builder instance + */ + @Bean + @ConditionalOnMissingBean + public WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } + + /** + * Creates the default ObjectMapper if none is provided. + * + *

    + * This ObjectMapper is used for JSON serialization and deserialization in the SSE + * transport implementation. + * @return the configured ObjectMapper instance + */ + @Bean + @ConditionalOnMissingBean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + +} diff --git a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/StdioTransportAutoConfiguration.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/StdioTransportAutoConfiguration.java new file mode 100644 index 00000000000..bc5e8d3c952 --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/StdioTransportAutoConfiguration.java @@ -0,0 +1,87 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.autoconfigure.mcp.client; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import io.modelcontextprotocol.client.transport.ServerParameters; +import io.modelcontextprotocol.client.transport.StdioClientTransport; +import io.modelcontextprotocol.spec.McpSchema; + +import org.springframework.ai.autoconfigure.mcp.client.properties.McpClientCommonProperties; +import org.springframework.ai.autoconfigure.mcp.client.properties.McpStdioClientProperties; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * Auto-configuration for Standard Input/Output (stdio) transport in the Model Context + * Protocol (MCP). + * + *

    + * This configuration class sets up the necessary beans for stdio-based transport, + * enabling communication with MCP servers through standard input and output streams. + * + *

    + * Key features: + *

      + *
    • Creates stdio transports for configured MCP server connections + *
    • Supports multiple named server connections with different parameters + *
    • Configures transport with server-specific parameters + *
    + * + * @see StdioClientTransport + * @see McpStdioClientProperties + */ +@AutoConfiguration +@ConditionalOnClass({ McpSchema.class }) +@EnableConfigurationProperties({ McpStdioClientProperties.class, McpClientCommonProperties.class }) +@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", + matchIfMissing = true) +public class StdioTransportAutoConfiguration { + + /** + * Creates a list of stdio-based transports for MCP communication. + * + *

    + * Each transport is configured with: + *

      + *
    • Server-specific parameters from properties + *
    • Unique connection name for identification + *
    + * @param sdioProperties the stdio client properties containing server configurations + * @return list of named MCP transports + */ + @Bean + public List stdioTransports(McpStdioClientProperties sdioProperties) { + + List stdoiTransports = new ArrayList<>(); + + for (Map.Entry serverParameters : sdioProperties.toServerParameters().entrySet()) { + var transport = new StdioClientTransport(serverParameters.getValue()); + stdoiTransports.add(new NamedClientMcpTransport(serverParameters.getKey(), transport)); + + } + + return stdoiTransports; + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/client/stdio/McpSyncClientConfigurer.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/configurer/McpAsyncClientConfigurer.java similarity index 62% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/client/stdio/McpSyncClientConfigurer.java rename to auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/configurer/McpAsyncClientConfigurer.java index 159a38057b0..70b33d7a0f6 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/client/stdio/McpSyncClientConfigurer.java +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/configurer/McpAsyncClientConfigurer.java @@ -14,30 +14,30 @@ * limitations under the License. */ -package org.springframework.ai.autoconfigure.mcp.client.stdio; +package org.springframework.ai.autoconfigure.mcp.client.configurer; import java.util.List; import io.modelcontextprotocol.client.McpClient; -import org.springframework.ai.mcp.McpSyncClientCustomizer; +import org.springframework.ai.mcp.customizer.McpAsyncClientCustomizer; -public class McpSyncClientConfigurer { +public class McpAsyncClientConfigurer { - private List customizers; + private List customizers; - void setCustomizers(List customizers) { + public McpAsyncClientConfigurer(List customizers) { this.customizers = customizers; } - public McpClient.SyncSpec configure(String name, McpClient.SyncSpec spec) { + public McpClient.AsyncSpec configure(String name, McpClient.AsyncSpec spec) { applyCustomizers(name, spec); return spec; } - private void applyCustomizers(String name, McpClient.SyncSpec spec) { + private void applyCustomizers(String name, McpClient.AsyncSpec spec) { if (this.customizers != null) { - for (McpSyncClientCustomizer customizer : this.customizers) { + for (McpAsyncClientCustomizer customizer : this.customizers) { customizer.customize(name, spec); } } diff --git a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/configurer/McpSyncClientConfigurer.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/configurer/McpSyncClientConfigurer.java new file mode 100644 index 00000000000..cc8c331cd9f --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/configurer/McpSyncClientConfigurer.java @@ -0,0 +1,76 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.autoconfigure.mcp.client.configurer; + +import java.util.List; + +import io.modelcontextprotocol.client.McpClient; + +import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer; + +/** + * Configurer class for customizing MCP synchronous clients. + * + *

    + * This class manages a collection of {@link McpSyncClientCustomizer} instances that can + * be applied to customize the configuration of MCP synchronous clients during their + * creation. + * + *

    + * The configurer applies customizations in the order they are registered, allowing for + * sequential modifications to the client specifications. + * + * @see McpSyncClientCustomizer + * @see McpClient.SyncSpec + */ +public class McpSyncClientConfigurer { + + private List customizers; + + public McpSyncClientConfigurer(List customizers) { + this.customizers = customizers; + } + + /** + * Configures an MCP sync client specification by applying all registered customizers. + * @param name the name of the client being configured + * @param spec the specification to customize + * @return the customized specification + */ + public McpClient.SyncSpec configure(String name, McpClient.SyncSpec spec) { + applyCustomizers(name, spec); + return spec; + } + + /** + * Applies all registered customizers to the given specification. + * + *

    + * Customizers are applied in the order they were registered. If no customizers are + * registered, this method has no effect. + * @param name the name of the client being customized + * @param spec the specification to customize + */ + private void applyCustomizers(String name, McpClient.SyncSpec spec) { + if (this.customizers != null) { + for (McpSyncClientCustomizer customizer : this.customizers) { + customizer.customize(name, spec); + } + } + } + +} diff --git a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpClientCommonProperties.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpClientCommonProperties.java new file mode 100644 index 00000000000..b7c73aae12b --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpClientCommonProperties.java @@ -0,0 +1,160 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.autoconfigure.mcp.client.properties; + +import java.time.Duration; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Common Configuration properties for the Model Context Protocol (MCP) clients shared for + * all transport types. + * + * @author Christian Tzolov + * @since 1.0.0 + */ +@ConfigurationProperties(McpClientCommonProperties.CONFIG_PREFIX) +public class McpClientCommonProperties { + + public static final String CONFIG_PREFIX = "spring.ai.mcp.client"; + + /** + * Enable/disable the MCP client. + *

    + * When set to false, the MCP client and all its components will not be initialized. + */ + private boolean enabled = true; + + /** + * The name of the MCP client instance. + *

    + * This name is reported to clients and used for compatibility checks. + */ + private String name = "spring-ai-mcp-client"; + + /** + * The version of the MCP client instance. + *

    + * This version is reported to clients and used for compatibility checks. + */ + private String version = "1.0.0"; + + /** + * Flag to indicate if the MCP client has to be initialized. + */ + private boolean initialized = true; + + /** + * The timeout duration for MCP client requests. + *

    + * Defaults to 20 seconds. + */ + private Duration requestTimeout = Duration.ofSeconds(20); + + /** + * The type of client to use for MCP client communication. + *

    + * Supported types are: + *

      + *
    • SYNC - Standard synchronous client (default)
    • + *
    • ASYNC - Asynchronous client
    • + *
    + */ + private ClientType type = ClientType.SYNC; + + /** + * Client types supported by the MCP client. + */ + public enum ClientType { + + /** + * Synchronous (McpSyncClient) client + */ + SYNC, + + /** + * Asynchronous (McpAsyncClient) client + */ + ASYNC + + } + + /** + * Flag to enable/disable root change notifications. + *

    + * When enabled, the client will be notified of changes to the root configuration. + * Defaults to true. + */ + private boolean rootChangeNotification = true; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return this.version; + } + + public void setVersion(String version) { + this.version = version; + } + + public boolean isInitialized() { + return this.initialized; + } + + public void setInitialized(boolean initialized) { + this.initialized = initialized; + } + + public Duration getRequestTimeout() { + return this.requestTimeout; + } + + public void setRequestTimeout(Duration requestTimeout) { + this.requestTimeout = requestTimeout; + } + + public ClientType getType() { + return this.type; + } + + public void setType(ClientType type) { + this.type = type; + } + + public boolean isRootChangeNotification() { + return this.rootChangeNotification; + } + + public void setRootChangeNotification(boolean rootChangeNotification) { + this.rootChangeNotification = rootChangeNotification; + } + +} diff --git a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpSseClientProperties.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpSseClientProperties.java new file mode 100644 index 00000000000..ed5575a1422 --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpSseClientProperties.java @@ -0,0 +1,73 @@ +/* +* Copyright 2024 - 2024 the original author or authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.springframework.ai.autoconfigure.mcp.client.properties; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for Server-Sent Events (SSE) based MCP client connections. + * + *

    + * These properties allow configuration of multiple named SSE connections to MCP servers. + * Each connection is configured with a URL endpoint for SSE communication. + * + *

    + * Example configuration:

    + * spring.ai.mcp.client.sse:
    + *   connections:
    + *     server1:
    + *       url: http://localhost:8080/events
    + *     server2:
    + *       url: http://otherserver:8081/events
    + * 
    + * + * @author Christian Tzolov + * @since 1.0.0 + * @see SseParameters + */ +@ConfigurationProperties(McpSseClientProperties.CONFIG_PREFIX) +public class McpSseClientProperties { + + public static final String CONFIG_PREFIX = "spring.ai.mcp.client.sse"; + + /** + * Parameters for configuring an SSE connection to an MCP server. + * + * @param url the URL endpoint for SSE communication with the MCP server + */ + public record SseParameters(String url) { + } + + /** + * Map of named SSE connection configurations. + *

    + * The key represents the connection name, and the value contains the SSE parameters + * for that connection. + */ + private final Map connections = new HashMap<>(); + + /** + * Returns the map of configured SSE connections. + * @return map of connection names to their SSE parameters + */ + public Map getConnections() { + return this.connections; + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/client/stdio/McpStdioClientProperties.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpStdioClientProperties.java similarity index 66% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/client/stdio/McpStdioClientProperties.java rename to auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpStdioClientProperties.java index 34a72f67aaa..1284b9d5100 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/client/stdio/McpStdioClientProperties.java +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpStdioClientProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.autoconfigure.mcp.client.stdio; +package org.springframework.ai.autoconfigure.mcp.client.properties; import java.time.Duration; import java.util.HashMap; @@ -47,35 +47,6 @@ public class McpStdioClientProperties { public static final String CONFIG_PREFIX = "spring.ai.mcp.client.stdio"; - /** - * Enable/disable the MCP client. - *

    - * When set to false, the MCP client and all its components will not be initialized. - */ - private boolean enabled = false; - - /** - * The version of the MCP client instance. - *

    - * This version is reported to clients and used for compatibility checks. - */ - private String version = "1.0.0"; - - /** - * The timeout duration for MCP client requests. - *

    - * Defaults to 20 seconds. - */ - private Duration requestTimeout = Duration.ofSeconds(20); - - /** - * Flag to enable/disable root change notifications. - *

    - * When enabled, the client will be notified of changes to the root configuration. - * Defaults to true. - */ - private boolean rootChangeNotification = true; - /** * Resource containing the MCP servers configuration. *

    @@ -90,12 +61,7 @@ public class McpStdioClientProperties { * Each entry represents a named connection with its specific configuration * parameters. */ - private final Map stdioConnections = new HashMap<>(); - - /** - * Flag to indicate if the MCP client has to be initialized. - */ - private boolean initialize = true; + private final Map connections = new HashMap<>(); public Resource getServersConfiguration() { return this.serversConfiguration; @@ -105,50 +71,8 @@ public void setServersConfiguration(Resource stdioConnectionResources) { this.serversConfiguration = stdioConnectionResources; } - public Map getStdioConnections() { - return this.stdioConnections; - } - - public boolean isRootChangeNotification() { - return this.rootChangeNotification; - } - - public void setRootChangeNotification(boolean rootChangeNotification) { - this.rootChangeNotification = rootChangeNotification; - } - - public Duration getRequestTimeout() { - return this.requestTimeout; - } - - public void setRequestTimeout(Duration requestTimeout) { - Assert.notNull(requestTimeout, "Request timeout must not be null"); - this.requestTimeout = requestTimeout; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getVersion() { - return this.version; - } - - public void setVersion(String version) { - Assert.hasText(version, "Version must not be empty"); - this.version = version; - } - - public boolean isInitialize() { - return this.initialize; - } - - public void setInitialize(boolean initialize) { - this.initialize = initialize; + public Map getConnections() { + return this.connections; } /** @@ -170,6 +94,11 @@ public record Parameters( * Map of environment variables for the server process. */ @JsonProperty("env") Map env) { + + public ServerParameters toServerParameters() { + return ServerParameters.builder(this.command()).args(this.args()).env(this.env()).build(); + } + } private Map resourceToServerParameters() { @@ -200,7 +129,7 @@ public Map toServerParameters() { serverParameters.putAll(resourceToServerParameters()); } - for (Map.Entry entry : this.stdioConnections.entrySet()) { + for (Map.Entry entry : this.connections.entrySet()) { serverParameters.put(entry.getKey(), entry.getValue().toServerParameters()); } return serverParameters; diff --git a/auto-configurations/spring-ai-mcp-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/spring-ai-mcp-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000000..f13b5f74875 --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,21 @@ +# +# Copyright 2025-2025 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +org.springframework.ai.autoconfigure.mcp.client.StdioTransportAutoConfiguration +org.springframework.ai.autoconfigure.mcp.client.SseWebFluxTransportAutoConfiguration +org.springframework.ai.autoconfigure.mcp.client.SseHttpClientTransportAutoConfiguration +org.springframework.ai.autoconfigure.mcp.client.McpClientAutoConfiguration + + diff --git a/auto-configurations/spring-ai-mcp-client/src/test/java/org/springframework/ai/autoconfigure/mcp/client/McpClientAutoConfigurationIT.java b/auto-configurations/spring-ai-mcp-client/src/test/java/org/springframework/ai/autoconfigure/mcp/client/McpClientAutoConfigurationIT.java new file mode 100644 index 00000000000..780d105d973 --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/src/test/java/org/springframework/ai/autoconfigure/mcp/client/McpClientAutoConfigurationIT.java @@ -0,0 +1,190 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.autoconfigure.mcp.client; + +import java.time.Duration; +import java.util.List; +import java.util.function.Function; + +import com.fasterxml.jackson.core.type.TypeReference; +import io.modelcontextprotocol.client.McpAsyncClient; +import io.modelcontextprotocol.client.McpSyncClient; +import io.modelcontextprotocol.spec.ClientMcpTransport; +import io.modelcontextprotocol.spec.McpSchema; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import reactor.core.publisher.Mono; + +import org.springframework.ai.autoconfigure.mcp.client.configurer.McpSyncClientConfigurer; +import org.springframework.ai.autoconfigure.mcp.client.properties.McpClientCommonProperties; +import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +@Disabled +public class McpClientAutoConfigurationIT { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(McpClientAutoConfiguration.class)); + + @Test + void defaultConfiguration() { + this.contextRunner.withUserConfiguration(TestTransportConfiguration.class).run(context -> { + List clients = context.getBean("mcpSyncClients", List.class); + assertThat(clients).hasSize(1); + + McpClientCommonProperties properties = context.getBean(McpClientCommonProperties.class); + assertThat(properties.getName()).isEqualTo("mcp-client"); + assertThat(properties.getVersion()).isEqualTo("1.0.0"); + assertThat(properties.getType()).isEqualTo(McpClientCommonProperties.ClientType.SYNC); + assertThat(properties.getRequestTimeout()).isEqualTo(Duration.ofSeconds(30)); + assertThat(properties.isInitialized()).isTrue(); + }); + } + + @Test + void asyncConfiguration() { + this.contextRunner + .withPropertyValues("spring.ai.mcp.client.type=ASYNC", "spring.ai.mcp.client.name=test-client", + "spring.ai.mcp.client.version=2.0.0", "spring.ai.mcp.client.request-timeout=60s", + "spring.ai.mcp.client.initialized=false") + .withUserConfiguration(TestTransportConfiguration.class) + .run(context -> { + List clients = context.getBean("mcpAsyncClients", List.class); + assertThat(clients).hasSize(1); + + McpClientCommonProperties properties = context.getBean(McpClientCommonProperties.class); + assertThat(properties.getName()).isEqualTo("test-client"); + assertThat(properties.getVersion()).isEqualTo("2.0.0"); + assertThat(properties.getType()).isEqualTo(McpClientCommonProperties.ClientType.ASYNC); + assertThat(properties.getRequestTimeout()).isEqualTo(Duration.ofSeconds(60)); + assertThat(properties.isInitialized()).isFalse(); + }); + } + + @Test + void disabledConfiguration() { + this.contextRunner.withPropertyValues("spring.ai.mcp.client.enabled=false").run(context -> { + assertThat(context).doesNotHaveBean(McpSyncClient.class); + assertThat(context).doesNotHaveBean(McpAsyncClient.class); + assertThat(context).doesNotHaveBean(ToolCallback.class); + }); + } + + @Test + void customTransportConfiguration() { + this.contextRunner.withUserConfiguration(CustomTransportConfiguration.class).run(context -> { + List transports = context.getBean("customTransports", List.class); + assertThat(transports).hasSize(1); + assertThat(transports.get(0).transport()).isInstanceOf(CustomClientTransport.class); + }); + } + + @Test + void clientCustomization() { + this.contextRunner.withUserConfiguration(TestTransportConfiguration.class, CustomizerConfiguration.class) + .run(context -> { + assertThat(context).hasSingleBean(McpSyncClientConfigurer.class); + List clients = context.getBean("mcpSyncClients", List.class); + assertThat(clients).hasSize(1); + }); + } + + @Test + void toolCallbacksCreation() { + this.contextRunner.withUserConfiguration(TestTransportConfiguration.class).run(context -> { + assertThat(context).hasSingleBean(List.class); + List callbacks = context.getBean("toolCallbacks", List.class); + assertThat(callbacks).isNotEmpty(); + }); + } + + @Test + void closeableWrappersCreation() { + this.contextRunner.withUserConfiguration(TestTransportConfiguration.class).run(context -> { + assertThat(context).hasSingleBean(McpClientAutoConfiguration.ClosebleMcpSyncClients.class); + }); + } + + @Configuration + static class TestTransportConfiguration { + + @Bean + List testTransports() { + return List.of(new NamedClientMcpTransport("test", Mockito.mock(ClientMcpTransport.class))); + } + + } + + @Configuration + static class CustomTransportConfiguration { + + @Bean + List customTransports() { + return List.of(new NamedClientMcpTransport("custom", new CustomClientTransport())); + } + + } + + @Configuration + static class CustomizerConfiguration { + + @Bean + McpSyncClientCustomizer testCustomizer() { + return (name, spec) -> { + /* no-op */ }; + } + + } + + static class CustomClientTransport implements ClientMcpTransport { + + @Override + public void close() { + // Test implementation + } + + @Override + public Mono connect( + Function, Mono> messageHandler) { + return Mono.empty(); // Test implementation + } + + @Override + public Mono sendMessage(McpSchema.JSONRPCMessage message) { + return Mono.empty(); // Test implementation + } + + @Override + public T unmarshalFrom(Object value, TypeReference type) { + return null; // Test implementation + } + + @Override + public Mono closeGracefully() { + return Mono.empty(); // Test implementation + } + + } + +} diff --git a/auto-configurations/spring-ai-mcp-client/src/test/resources/application-test.properties b/auto-configurations/spring-ai-mcp-client/src/test/resources/application-test.properties new file mode 100644 index 00000000000..9107b9e407a --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/src/test/resources/application-test.properties @@ -0,0 +1,10 @@ +# Test MCP STDIO client configuration +spring.ai.mcp.client.stdio.enabled=true +spring.ai.mcp.client.stdio.version=test-version +spring.ai.mcp.client.stdio.request-timeout=15s +spring.ai.mcp.client.stdio.root-change-notification=false + +# Test server configuration +spring.ai.mcp.client.stdio.stdio-connections.test-server.command=echo +spring.ai.mcp.client.stdio.stdio-connections.test-server.args[0]=test +spring.ai.mcp.client.stdio.stdio-connections.test-server.env.TEST_ENV=test-value diff --git a/auto-configurations/spring-ai-mcp-server/pom.xml b/auto-configurations/spring-ai-mcp-server/pom.xml new file mode 100644 index 00000000000..751884f8aec --- /dev/null +++ b/auto-configurations/spring-ai-mcp-server/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + org.springframework.ai + spring-ai + 1.0.0-SNAPSHOT + ../../pom.xml + + spring-ai-mcp-server-spring-boot-autoconfigure + jar + Spring AI MCP Server Auto Configuration + Spring AI MCP Server Auto Configuration + https://github.com/spring-projects/spring-ai + + + https://github.com/spring-projects/spring-ai + git://github.com/spring-projects/spring-ai.git + git@github.com:spring-projects/spring-ai.git + + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.ai + spring-ai-mcp + ${project.parent.version} + true + + + + io.modelcontextprotocol.sdk + mcp-spring-webflux + true + + + + io.modelcontextprotocol.sdk + mcp-spring-webmvc + true + + + + + + org.springframework.ai + spring-ai-test + ${project.parent.version} + test + + + + + + diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/McpServerProperties.java b/auto-configurations/spring-ai-mcp-server/src/main/java/org/springframework/ai/autoconfigure/mcp/server/McpServerProperties.java similarity index 82% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/McpServerProperties.java rename to auto-configurations/spring-ai-mcp-server/src/main/java/org/springframework/ai/autoconfigure/mcp/server/McpServerProperties.java index 5be80b131c6..00867c089d4 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/McpServerProperties.java +++ b/auto-configurations/spring-ai-mcp-server/src/main/java/org/springframework/ai/autoconfigure/mcp/server/McpServerProperties.java @@ -25,7 +25,6 @@ * These properties control the behavior and configuration of the MCP server, including: *

      *
    • Server identification (name and version)
    • - *
    • Transport type (STDIO, WEBMVC, or WEBFLUX)
    • *
    • Change notification settings for tools, resources, and prompts
    • *
    • Web transport endpoint configuration
    • *
    @@ -46,7 +45,15 @@ public class McpServerProperties { *

    * When set to false, the MCP server and all its components will not be initialized. */ - private boolean enabled = false; + private boolean enabled = true; + + /** + * Enable/disable the standard input/output (stdio) transport. + *

    + * When enabled, the server will listen for incoming messages on the standard input + * and write responses to the standard output. + */ + private boolean stdio = false; /** * The name of the MCP server instance. @@ -88,18 +95,6 @@ public class McpServerProperties { */ private boolean promptChangeNotification = true; - /** - * The transport type to use for MCP server communication. - *

    - * Supported types are: - *

      - *
    • STDIO - Standard input/output transport (default)
    • - *
    • WEBMVC - Spring MVC Server-Sent Events transport
    • - *
    • WEBFLUX - Spring WebFlux Server-Sent Events transport
    • - *
    - */ - private Transport transport = Transport.STDIO; - /** * The endpoint path for Server-Sent Events (SSE) when using web transports. *

    @@ -118,31 +113,6 @@ public class McpServerProperties { */ private ServerType type = ServerType.SYNC; - /** - * Transport types supported by the MCP server. - */ - public enum Transport { - - /** - * Standard input/output transport, suitable for command-line tools and local - * development. - */ - STDIO, - - /** - * Spring MVC Server-Sent Events transport, requires spring-boot-starter-web and - * mcp-spring-webmvc. - */ - WEBMVC, - - /** - * Spring WebFlux Server-Sent Events transport, requires - * spring-boot-starter-webflux and mcp-spring-webflux. - */ - WEBFLUX - - } - /** * Server types supported by the MCP server. */ @@ -160,6 +130,14 @@ public enum ServerType { } + public boolean isStdio() { + return this.stdio; + } + + public void setStdio(boolean stdio) { + this.stdio = stdio; + } + public boolean isEnabled() { return this.enabled; } @@ -210,15 +188,6 @@ public void setPromptChangeNotification(boolean promptChangeNotification) { this.promptChangeNotification = promptChangeNotification; } - public Transport getTransport() { - return this.transport; - } - - public void setTransport(Transport transport) { - Assert.notNull(transport, "Transport must not be null"); - this.transport = transport; - } - public String getSseMessageEndpoint() { return this.sseMessageEndpoint; } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcServerAutoConfiguration.java b/auto-configurations/spring-ai-mcp-server/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcServerAutoConfiguration.java similarity index 72% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcServerAutoConfiguration.java rename to auto-configurations/spring-ai-mcp-server/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcServerAutoConfiguration.java index 1293f3d97a8..dfb8f9760f7 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcServerAutoConfiguration.java +++ b/auto-configurations/spring-ai-mcp-server/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcServerAutoConfiguration.java @@ -20,19 +20,23 @@ import java.util.function.Consumer; import java.util.function.Function; +import io.modelcontextprotocol.server.McpAsyncServer; import io.modelcontextprotocol.server.McpServer; -import reactor.core.publisher.Mono; -import io.modelcontextprotocol.server.McpServer.SyncSpec; import io.modelcontextprotocol.server.McpServer.AsyncSpec; +import io.modelcontextprotocol.server.McpServer.SyncSpec; import io.modelcontextprotocol.server.McpServerFeatures; -import io.modelcontextprotocol.server.McpServerFeatures.SyncToolRegistration; +import io.modelcontextprotocol.server.McpServerFeatures.AsyncPromptRegistration; +import io.modelcontextprotocol.server.McpServerFeatures.AsyncResourceRegistration; import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolRegistration; +import io.modelcontextprotocol.server.McpServerFeatures.SyncPromptRegistration; +import io.modelcontextprotocol.server.McpServerFeatures.SyncResourceRegistration; +import io.modelcontextprotocol.server.McpServerFeatures.SyncToolRegistration; import io.modelcontextprotocol.server.McpSyncServer; -import io.modelcontextprotocol.server.McpAsyncServer; import io.modelcontextprotocol.server.transport.StdioServerTransport; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.Implementation; import io.modelcontextprotocol.spec.ServerMcpTransport; +import reactor.core.publisher.Mono; import org.springframework.ai.mcp.McpToolUtils; import org.springframework.ai.tool.ToolCallback; @@ -45,6 +49,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.core.log.LogAccessor; +import org.springframework.util.CollectionUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for the Model Context Protocol (MCP) @@ -94,18 +99,17 @@ * @see MpcWebMvcServerAutoConfiguration * @see org.springframework.ai.mcp.ToolCallback */ -@AutoConfiguration +@AutoConfiguration(after = { MpcWebMvcServerAutoConfiguration.class, MpcWebFluxServerAutoConfiguration.class }) @ConditionalOnClass({ McpSchema.class, McpSyncServer.class }) @EnableConfigurationProperties(McpServerProperties.class) -@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true") +@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", + matchIfMissing = true) public class MpcServerAutoConfiguration { private static final LogAccessor logger = new LogAccessor(MpcServerAutoConfiguration.class); @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "transport", havingValue = "STDIO", - matchIfMissing = true) public ServerMcpTransport stdioServerTransport() { return new StdioServerTransport(); } @@ -119,8 +123,9 @@ public McpSchema.ServerCapabilities.Builder capabilitiesBuilder() { @Bean @ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", matchIfMissing = true) - public List syncTools(List toolCalls) { - return McpToolUtils.toSyncToolRegistration(toolCalls); + public List syncTools(ObjectProvider> toolCalls) { + var tools = toolCalls.stream().flatMap(List::stream).toList(); + return McpToolUtils.toSyncToolRegistration(tools); } @Bean @@ -128,9 +133,8 @@ public List syncTools(List matchIfMissing = true) public McpSyncServer mcpSyncServer(ServerMcpTransport transport, McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties, - ObjectProvider> tools, - ObjectProvider> resources, - ObjectProvider> prompts, + ObjectProvider> tools, ObjectProvider> resources, + ObjectProvider> prompts, ObjectProvider>> rootsChangeConsumers) { McpSchema.Implementation serverInfo = new Implementation(serverProperties.getName(), @@ -139,26 +143,29 @@ public McpSyncServer mcpSyncServer(ServerMcpTransport transport, // Create the server with both tool and resource capabilities SyncSpec serverBuilder = McpServer.sync(transport).serverInfo(serverInfo); - tools.ifAvailable(toolsList -> { - serverBuilder.tools(toolsList); + List toolResgistrations = tools.stream().flatMap(List::stream).toList(); + if (!CollectionUtils.isEmpty(toolResgistrations)) { + serverBuilder.tools(toolResgistrations); capabilitiesBuilder.tools(serverProperties.isToolChangeNotification()); - logger.info("Registered tools" + toolsList.size() + " notification: " + logger.info("Registered tools" + toolResgistrations.size() + " notification: " + serverProperties.isToolChangeNotification()); - }); + } - resources.ifAvailable(resourceList -> { - serverBuilder.resources(resourceList); + List resourceResgistrations = resources.stream().flatMap(List::stream).toList(); + if (!CollectionUtils.isEmpty(resourceResgistrations)) { + serverBuilder.resources(resourceResgistrations); capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification()); - logger.info("Registered resources" + resourceList.size() + " notification: " + logger.info("Registered resources" + resourceResgistrations.size() + " notification: " + serverProperties.isResourceChangeNotification()); - }); + } - prompts.ifAvailable(promptList -> { - serverBuilder.prompts(promptList); + List promptResgistrations = prompts.stream().flatMap(List::stream).toList(); + if (!CollectionUtils.isEmpty(promptResgistrations)) { + serverBuilder.prompts(promptResgistrations); capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification()); - logger.info("Registered prompts" + promptList.size() + " notification: " + logger.info("Registered prompts" + promptResgistrations.size() + " notification: " + serverProperties.isPromptChangeNotification()); - }); + } rootsChangeConsumers.ifAvailable(consumer -> { serverBuilder.rootsChangeConsumer(consumer); @@ -172,8 +179,9 @@ public McpSyncServer mcpSyncServer(ServerMcpTransport transport, @Bean @ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") - public List asyncTools(List toolCalls) { - return McpToolUtils.toAsyncToolRegistration(toolCalls); + public List asyncTools(ObjectProvider> toolCalls) { + var tools = toolCalls.stream().flatMap(List::stream).toList(); + return McpToolUtils.toAsyncToolRegistration(tools); } @Bean @@ -181,8 +189,8 @@ public List asyncTools(List> tools, - ObjectProvider> resources, - ObjectProvider> prompts, + ObjectProvider> resources, + ObjectProvider> prompts, ObjectProvider>> rootsChangeConsumer) { McpSchema.Implementation serverInfo = new Implementation(serverProperties.getName(), @@ -191,26 +199,29 @@ public McpAsyncServer mcpAsyncServer(ServerMcpTransport transport, // Create the server with both tool and resource capabilities AsyncSpec serverBilder = McpServer.async(transport).serverInfo(serverInfo); - tools.ifAvailable(toolsList -> { - serverBilder.tools(toolsList); + List toolResgistrations = tools.stream().flatMap(List::stream).toList(); + if (!CollectionUtils.isEmpty(toolResgistrations)) { + serverBilder.tools(toolResgistrations); capabilitiesBuilder.tools(serverProperties.isToolChangeNotification()); - logger.info("Registered tools" + toolsList.size() + " notification: " + logger.info("Registered tools" + toolResgistrations.size() + " notification: " + serverProperties.isToolChangeNotification()); - }); + } - resources.ifAvailable(resourceList -> { - serverBilder.resources(resourceList); + List resourceResgistrations = resources.stream().flatMap(List::stream).toList(); + if (!CollectionUtils.isEmpty(resourceResgistrations)) { + serverBilder.resources(resourceResgistrations); capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification()); - logger.info("Registered resources" + resourceList.size() + " notification: " + logger.info("Registered resources" + resourceResgistrations.size() + " notification: " + serverProperties.isResourceChangeNotification()); - }); + } - prompts.ifAvailable(promptList -> { - serverBilder.prompts(promptList); + List promptResgistrations = prompts.stream().flatMap(List::stream).toList(); + if (!CollectionUtils.isEmpty(promptResgistrations)) { + serverBilder.prompts(promptResgistrations); capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification()); - logger.info("Registered prompts" + promptList.size() + " notification: " + logger.info("Registered prompts" + promptResgistrations.size() + " notification: " + serverProperties.isPromptChangeNotification()); - }); + } rootsChangeConsumer.ifAvailable(consumer -> { Function, Mono> asyncConsumer = roots -> { diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebFluxServerAutoConfiguration.java b/auto-configurations/spring-ai-mcp-server/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebFluxServerAutoConfiguration.java similarity index 94% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebFluxServerAutoConfiguration.java rename to auto-configurations/spring-ai-mcp-server/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebFluxServerAutoConfiguration.java index a1ce31bca5b..703ee484350 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebFluxServerAutoConfiguration.java +++ b/auto-configurations/spring-ai-mcp-server/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebFluxServerAutoConfiguration.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebFluxSseServerTransport; +import io.modelcontextprotocol.spec.ServerMcpTransport; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -64,7 +65,9 @@ */ @AutoConfiguration @ConditionalOnClass({ WebFluxSseServerTransport.class }) -@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "transport", havingValue = "WEBFLUX") +@ConditionalOnMissingBean(ServerMcpTransport.class) +@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "stdio", havingValue = "false", + matchIfMissing = true) public class MpcWebFluxServerAutoConfiguration { @Bean diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebMvcServerAutoConfiguration.java b/auto-configurations/spring-ai-mcp-server/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebMvcServerAutoConfiguration.java similarity index 94% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebMvcServerAutoConfiguration.java rename to auto-configurations/spring-ai-mcp-server/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebMvcServerAutoConfiguration.java index 5759cc4f68c..55d19df49d9 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebMvcServerAutoConfiguration.java +++ b/auto-configurations/spring-ai-mcp-server/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebMvcServerAutoConfiguration.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebMvcSseServerTransport; +import io.modelcontextprotocol.spec.ServerMcpTransport; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -48,10 +49,6 @@ *

    * Required dependencies:

    {@code
      * 
    - *     io.modelcontextprotocol.sdk
    - *     mcp-spring-webmvc
    - * 
    - * 
      *     org.springframework.boot
      *     spring-boot-starter-web
      * 
    @@ -64,7 +61,9 @@
      */
     @AutoConfiguration
     @ConditionalOnClass({ WebMvcSseServerTransport.class })
    -@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "transport", havingValue = "WEBMVC")
    +@ConditionalOnMissingBean(ServerMcpTransport.class)
    +@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "stdio", havingValue = "false",
    +		matchIfMissing = true)
     public class MpcWebMvcServerAutoConfiguration {
     
     	@Bean
    diff --git a/auto-configurations/spring-ai-mcp-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/spring-ai-mcp-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
    new file mode 100644
    index 00000000000..eee7e32e69c
    --- /dev/null
    +++ b/auto-configurations/spring-ai-mcp-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
    @@ -0,0 +1,20 @@
    +#
    +# Copyright 2025-2025 the original author or authors.
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#      https://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +#
    +
    +org.springframework.ai.autoconfigure.mcp.server.MpcServerAutoConfiguration
    +org.springframework.ai.autoconfigure.mcp.server.MpcWebMvcServerAutoConfiguration
    +org.springframework.ai.autoconfigure.mcp.server.MpcWebFluxServerAutoConfiguration
    +
    diff --git a/auto-configurations/spring-ai-mcp-server/src/test/java/org/springframework/ai/autoconfigure/mcp/server/McpServerAutoConfigurationIT.java b/auto-configurations/spring-ai-mcp-server/src/test/java/org/springframework/ai/autoconfigure/mcp/server/McpServerAutoConfigurationIT.java
    new file mode 100644
    index 00000000000..34490ea5393
    --- /dev/null
    +++ b/auto-configurations/spring-ai-mcp-server/src/test/java/org/springframework/ai/autoconfigure/mcp/server/McpServerAutoConfigurationIT.java
    @@ -0,0 +1,311 @@
    +/*
    + * Copyright 2025-2025 the original author or authors.
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      https://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.springframework.ai.autoconfigure.mcp.server;
    +
    +import com.fasterxml.jackson.core.type.TypeReference;
    +import io.modelcontextprotocol.client.McpSyncClient;
    +import io.modelcontextprotocol.server.McpAsyncServer;
    +import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolRegistration;
    +import io.modelcontextprotocol.server.McpServerFeatures.SyncToolRegistration;
    +import io.modelcontextprotocol.server.McpServerFeatures.SyncResourceRegistration;
    +import io.modelcontextprotocol.server.McpServerFeatures.SyncPromptRegistration;
    +import io.modelcontextprotocol.server.McpSyncServer;
    +import io.modelcontextprotocol.server.transport.StdioServerTransport;
    +import io.modelcontextprotocol.spec.McpSchema;
    +import io.modelcontextprotocol.spec.ServerMcpTransport;
    +import org.mockito.Mockito;
    +import org.junit.jupiter.api.Test;
    +import org.springframework.ai.mcp.SyncMcpToolCallback;
    +import org.springframework.ai.tool.ToolCallback;
    +import org.springframework.boot.autoconfigure.AutoConfigurations;
    +import org.springframework.boot.test.context.runner.ApplicationContextRunner;
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +import reactor.core.publisher.Mono;
    +
    +import java.util.List;
    +import java.util.function.Consumer;
    +import java.util.function.Function;
    +
    +import static org.assertj.core.api.Assertions.assertThat;
    +
    +public class McpServerAutoConfigurationIT {
    +
    +	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
    +		.withConfiguration(AutoConfigurations.of(MpcServerAutoConfiguration.class));
    +
    +	@Test
    +	void defaultConfiguration() {
    +		this.contextRunner.run(context -> {
    +			assertThat(context).hasSingleBean(McpSyncServer.class);
    +			assertThat(context).hasSingleBean(ServerMcpTransport.class);
    +			assertThat(context.getBean(ServerMcpTransport.class)).isInstanceOf(StdioServerTransport.class);
    +
    +			McpServerProperties properties = context.getBean(McpServerProperties.class);
    +			assertThat(properties.getName()).isEqualTo("mcp-server");
    +			assertThat(properties.getVersion()).isEqualTo("1.0.0");
    +			assertThat(properties.getType()).isEqualTo(McpServerProperties.ServerType.SYNC);
    +			assertThat(properties.isToolChangeNotification()).isTrue();
    +			assertThat(properties.isResourceChangeNotification()).isTrue();
    +			assertThat(properties.isPromptChangeNotification()).isTrue();
    +		});
    +	}
    +
    +	@Test
    +	void asyncConfiguration() {
    +		this.contextRunner
    +			.withPropertyValues("spring.ai.mcp.server.type=ASYNC", "spring.ai.mcp.server.name=test-server",
    +					"spring.ai.mcp.server.version=2.0.0")
    +			.run(context -> {
    +				assertThat(context).hasSingleBean(McpAsyncServer.class);
    +				assertThat(context).doesNotHaveBean(McpSyncServer.class);
    +
    +				McpServerProperties properties = context.getBean(McpServerProperties.class);
    +				assertThat(properties.getName()).isEqualTo("test-server");
    +				assertThat(properties.getVersion()).isEqualTo("2.0.0");
    +				assertThat(properties.getType()).isEqualTo(McpServerProperties.ServerType.ASYNC);
    +			});
    +	}
    +
    +	@Test
    +	void transportConfiguration() {
    +		this.contextRunner.withUserConfiguration(CustomTransportConfiguration.class).run(context -> {
    +			assertThat(context).hasSingleBean(ServerMcpTransport.class);
    +			assertThat(context.getBean(ServerMcpTransport.class)).isInstanceOf(CustomServerTransport.class);
    +		});
    +	}
    +
    +	@Test
    +	void serverNotificationConfiguration() {
    +		this.contextRunner
    +			.withPropertyValues("spring.ai.mcp.server.tool-change-notification=false",
    +					"spring.ai.mcp.server.resource-change-notification=false")
    +			.run(context -> {
    +				McpServerProperties properties = context.getBean(McpServerProperties.class);
    +				assertThat(properties.isToolChangeNotification()).isFalse();
    +				assertThat(properties.isResourceChangeNotification()).isFalse();
    +			});
    +	}
    +
    +	// @Test
    +	void invalidConfigurationThrowsException() {
    +		this.contextRunner.withPropertyValues("spring.ai.mcp.server.version=invalid-version").run(context -> {
    +			assertThat(context).hasFailed();
    +			assertThat(context).getFailure()
    +				.hasRootCauseInstanceOf(IllegalArgumentException.class)
    +				.hasMessageContaining("Invalid version format");
    +		});
    +	}
    +
    +	@Test
    +	void disabledConfiguration() {
    +		this.contextRunner.withPropertyValues("spring.ai.mcp.server.enabled=false").run(context -> {
    +			assertThat(context).doesNotHaveBean(McpSyncServer.class);
    +			assertThat(context).doesNotHaveBean(McpAsyncServer.class);
    +			assertThat(context).doesNotHaveBean(ServerMcpTransport.class);
    +		});
    +	}
    +
    +	@Test
    +	void notificationConfiguration() {
    +		this.contextRunner
    +			.withPropertyValues("spring.ai.mcp.server.tool-change-notification=false",
    +					"spring.ai.mcp.server.resource-change-notification=false",
    +					"spring.ai.mcp.server.prompt-change-notification=false")
    +			.run(context -> {
    +				McpServerProperties properties = context.getBean(McpServerProperties.class);
    +				assertThat(properties.isToolChangeNotification()).isFalse();
    +				assertThat(properties.isResourceChangeNotification()).isFalse();
    +				assertThat(properties.isPromptChangeNotification()).isFalse();
    +			});
    +	}
    +
    +	@Test
    +	void stdioConfiguration() {
    +		this.contextRunner.withPropertyValues("spring.ai.mcp.server.stdio=true").run(context -> {
    +			McpServerProperties properties = context.getBean(McpServerProperties.class);
    +			assertThat(properties.isStdio()).isTrue();
    +		});
    +	}
    +
    +	@Test
    +	void serverCapabilitiesConfiguration() {
    +		this.contextRunner.run(context -> {
    +			assertThat(context).hasSingleBean(McpSchema.ServerCapabilities.Builder.class);
    +			McpSchema.ServerCapabilities.Builder builder = context.getBean(McpSchema.ServerCapabilities.Builder.class);
    +			assertThat(builder).isNotNull();
    +		});
    +	}
    +
    +	@Test
    +	void toolRegistrationConfiguration() {
    +		this.contextRunner.withUserConfiguration(TestToolConfiguration.class).run(context -> {
    +			List tools = context.getBean("syncTools", List.class);
    +			assertThat(tools).hasSize(1);
    +		});
    +	}
    +
    +	@Test
    +	void resourceRegistrationConfiguration() {
    +		this.contextRunner.withUserConfiguration(TestResourceConfiguration.class).run(context -> {
    +			McpSyncServer server = context.getBean(McpSyncServer.class);
    +			assertThat(server).isNotNull();
    +		});
    +	}
    +
    +	@Test
    +	void promptRegistrationConfiguration() {
    +		this.contextRunner.withUserConfiguration(TestPromptConfiguration.class).run(context -> {
    +			McpSyncServer server = context.getBean(McpSyncServer.class);
    +			assertThat(server).isNotNull();
    +		});
    +	}
    +
    +	@Test
    +	void asyncToolRegistrationConfiguration() {
    +		this.contextRunner.withPropertyValues("spring.ai.mcp.server.type=ASYNC")
    +			.withUserConfiguration(TestToolConfiguration.class)
    +			.run(context -> {
    +				List tools = context.getBean("asyncTools", List.class);
    +				assertThat(tools).hasSize(1);
    +			});
    +	}
    +
    +	@Test
    +	void customCapabilitiesBuilder() {
    +		this.contextRunner.withUserConfiguration(CustomCapabilitiesConfiguration.class).run(context -> {
    +			assertThat(context).hasSingleBean(McpSchema.ServerCapabilities.Builder.class);
    +			assertThat(context.getBean(McpSchema.ServerCapabilities.Builder.class))
    +				.isInstanceOf(CustomCapabilitiesBuilder.class);
    +		});
    +	}
    +
    +	@Test
    +	void rootsChangeConsumerConfiguration() {
    +		this.contextRunner.withUserConfiguration(TestRootsChangeConfiguration.class).run(context -> {
    +			McpSyncServer server = context.getBean(McpSyncServer.class);
    +			assertThat(server).isNotNull();
    +		});
    +	}
    +
    +	@Configuration
    +	static class TestResourceConfiguration {
    +
    +		@Bean
    +		List testResources() {
    +			return List.of();
    +		}
    +
    +	}
    +
    +	@Configuration
    +	static class TestPromptConfiguration {
    +
    +		@Bean
    +		List testPrompts() {
    +			return List.of();
    +		}
    +
    +	}
    +
    +	@Configuration
    +	static class CustomCapabilitiesConfiguration {
    +
    +		@Bean
    +		McpSchema.ServerCapabilities.Builder customCapabilitiesBuilder() {
    +			return new CustomCapabilitiesBuilder();
    +		}
    +
    +	}
    +
    +	static class CustomCapabilitiesBuilder extends McpSchema.ServerCapabilities.Builder {
    +
    +		// Custom implementation for testing
    +
    +	}
    +
    +	@Configuration
    +	static class TestToolConfiguration {
    +
    +		@Bean
    +		List testTool() {
    +			McpSyncClient mockClient = Mockito.mock(McpSyncClient.class);
    +			McpSchema.Tool mockTool = Mockito.mock(McpSchema.Tool.class);
    +			McpSchema.CallToolResult mockResult = Mockito.mock(McpSchema.CallToolResult.class);
    +
    +			Mockito.when(mockTool.name()).thenReturn("test-tool");
    +			Mockito.when(mockTool.description()).thenReturn("Test Tool");
    +			Mockito.when(mockClient.callTool(Mockito.any(McpSchema.CallToolRequest.class))).thenReturn(mockResult);
    +
    +			return List.of(new SyncMcpToolCallback(mockClient, mockTool));
    +		}
    +
    +	}
    +
    +	@Configuration
    +	static class TestRootsChangeConfiguration {
    +
    +		@Bean
    +		Consumer> rootsChangeConsumer() {
    +			return roots -> {
    +				// Test implementation
    +			};
    +		}
    +
    +	}
    +
    +	static class CustomServerTransport implements ServerMcpTransport {
    +
    +		@Override
    +		public Mono connect(
    +				Function, Mono> messageHandler) {
    +			return Mono.empty(); // Test implementation
    +		}
    +
    +		@Override
    +		public Mono sendMessage(McpSchema.JSONRPCMessage message) {
    +			return Mono.empty(); // Test implementation
    +		}
    +
    +		@Override
    +		public  T unmarshalFrom(Object value, TypeReference type) {
    +			return null; // Test implementation
    +		}
    +
    +		@Override
    +		public void close() {
    +			// Test implementation
    +		}
    +
    +		@Override
    +		public Mono closeGracefully() {
    +			return Mono.empty(); // Test implementation
    +		}
    +
    +	}
    +
    +	@Configuration
    +	static class CustomTransportConfiguration {
    +
    +		@Bean
    +		ServerMcpTransport customTransport() {
    +			return new CustomServerTransport();
    +		}
    +
    +	}
    +
    +}
    diff --git a/mcp-client-boot-starter-docs.adoc b/mcp-client-boot-starter-docs.adoc
    new file mode 100644
    index 00000000000..a43ec00a64a
    --- /dev/null
    +++ b/mcp-client-boot-starter-docs.adoc
    @@ -0,0 +1,340 @@
    += Spring AI MCP Client Boot Starter
    +
    +The Spring AI MCP (Model Context Protocol) Client Boot Starter provides auto-configuration for MCP client functionality in Spring Boot applications. It supports both synchronous and asynchronous client implementations with various transport options.
    +
    +The MCP Client Boot Starter provides:
    +
    +* Automatic client initialization (if enabled)
    +* Support for multiple named transports
    +* Integration with Spring AI's tool execution framework
    +* Proper lifecycle management with automatic cleanup
    +* Customizable client creation through customizers
    +
    +
    +== Dependencies
    +
    +=== Core Starter
    +
    +[source,xml]
    +----
    +
    +    org.springframework.ai
    +    spring-ai-mcp-client-spring-boot-starter
    +    ${spring-ai.version}
    +
    +----
    +
    +It will connect, simultaneously, to one or more MCP Servers over `STDIO` (in-process) and/or `SSE` (remote) transports.
    +The SSE connection uses the HttpClient-based transport implementation.
    +Every connection to an MCP Server creates a new MCP Client instance.
    +You can opt for either `SYNC` or `ASYNC` MCP Clients (Note: you cannot mix sync and async clients).
    +For more enterprise-ready deployment, it is recommended to use the WebFlux-based SSE connection using the `spring-ai-mcp-client-webflux-spring-boot-starter` starter.
    +
    +=== WebFlux Starter
    +
    +Similar to the core starter, it allows configuring one or more STDIO and SSE connections, but uses WebFlux-based SSE transport implementation.
    +
    +[source,xml]
    +----
    +
    +    org.springframework.ai
    +    spring-ai-mcp-client-webflux-spring-boot-starter
    +    ${spring-ai.version}
    +
    +----
    +
    +== Configuration Properties
    +
    +=== Common Properties
    +
    +All common configuration properties are prefixed with `spring.ai.mcp.client`:
    +
    +[cols="3,4,3"]
    +|===
    +|Property |Description |Default Value
    +
    +|`enabled`
    +|Enable/disable the MCP client
    +|`true`
    +
    +|`name`
    +|Name of the MCP client instance (used for compatibility checks)
    +|`spring-ai-mcp-client`
    +
    +|`version`
    +|Version of the MCP client instance
    +|`1.0.0`
    +
    +|`initialized`
    +|Whether to initialize clients on creation
    +|`true`
    +
    +|`request-timeout`
    +|Timeout duration for MCP client requests
    +|`20s`
    +
    +|`type`
    +|Client type (SYNC or ASYNC). You can not mix client type. All clients can be either sync or async
    +|`SYNC`
    +
    +|`root-change-notification`
    +|Enable/disable root change notifications for all clients
    +|`true`
    +|===
    +
    +=== SSE Transport Properties
    +
    +Properties for Server-Sent Events (SSE) transport are prefixed with `spring.ai.mcp.client.sse`:
    +
    +[cols="2,4"]
    +|===
    +|Property |Description
    +
    +|`connections`
    +|Map of named SSE connection configurations
    +
    +|`connections.[name].url`
    +|URL endpoint for SSE communication with the MCP server
    +|===
    +
    +Example configuration:
    +[source,yaml]
    +----
    +spring:
    +  ai:
    +    mcp:
    +      client:
    +        sse:
    +          connections:
    +            server1:
    +              url: http://localhost:8080
    +            server2:
    +              url: http://otherserver:8081
    +----
    +
    +=== Stdio Transport Properties
    +
    +Properties for Standard I/O transport are prefixed with `spring.ai.mcp.client.stdio`:
    +
    +[cols="3,4,3"]
    +|===
    +|Property |Description |Default Value
    +
    +|`servers-configuration`
    +|Resource containing the MCP servers configuration in JSON format
    +|-
    +
    +|`connections`
    +|Map of named stdio connection configurations
    +|-
    +
    +|`connections.[name].command`
    +|The command to execute for the MCP server
    +|-
    +
    +|`connections.[name].args`
    +|List of command arguments
    +|-
    +
    +|`connections.[name].env`
    +|Map of environment variables for the server process
    +|-
    +|===
    +
    +Example configuration:
    +[source,yaml]
    +----
    +spring:
    +  ai:
    +    mcp:
    +      client:
    +        stdio:
    +          root-change-notification: true
    +          connections:
    +            server1:
    +              command: /path/to/server
    +              args:
    +                - --port=8080
    +                - --mode=production
    +              env:
    +                API_KEY: your-api-key
    +                DEBUG: "true"
    +----
    +
    +Alternatively, you can configure stdio connections using an external JSON file using the link:https://modelcontextprotocol.io/quickstart/user[Claude Desctop format]:
    +
    +[source,yaml]
    +----
    +spring:
    +  ai:
    +    mcp:
    +      client:
    +        stdio:
    +          servers-configuration: classpath:mcp-servers.json
    +----
    +
    +The Claude Destop format looks like this:
    +
    +[source,json]
    +----
    +{
    +  "mcpServers": {
    +    "filesystem": {
    +      "command": "npx",
    +      "args": [
    +        "-y",
    +        "@modelcontextprotocol/server-filesystem",
    +        "/Users/username/Desktop",
    +        "/Users/username/Downloads"
    +      ]
    +    }
    +  }
    +}
    +----
    +Currently the Claude Destop supports only STDIO connection types.
    +
    +== Features
    +
    +=== Client Types
    +
    +The starter supports two types of clients:
    +
    +1. *Synchronous Client (SYNC)*
    +   * Default client type
    +   * Blocking operations
    +   * Suitable for traditional request-response patterns
    +
    +2. *Asynchronous Client (ASYNC)*
    +   * Non-blocking operations
    +   * Suitable for reactive applications
    +   * Must be explicitly configured using `spring.ai.mcp.client.type=ASYNC`
    +
    +=== Client Customization
    +
    +The auto-configuration supports customization through:
    +
    +* `McpSyncClientCustomizer` for synchronous clients
    +* `McpAsyncClientCustomizer` for asynchronous clients
    +
    +== Usage Example
    +
    +1. Add the appropriate starter dependency to your project.
    +
    +2. Configure the client in `application.properties` or `application.yml`:
    +
    +[source,yaml]
    +----
    +spring:
    +  ai:
    +    mcp:
    +      client:
    +        enabled: true
    +        name: my-mcp-client
    +        version: 1.0.0
    +        request-timeout: 30s
    +        type: SYNC  # or ASYNC for reactive applications
    +        sse:
    +          connections:
    +            server1:
    +              url: http://localhost:8080
    +            server2:
    +              url: http://otherserver:8081        
    +        stdio:
    +          root-change-notification: false
    +          connections:
    +            server1:
    +              command: /path/to/server
    +              args:
    +                - --port=8080
    +                - --mode=production
    +              env:
    +                API_KEY: your-api-key
    +                DEBUG: "true"
    +----
    +
    +3. The MCP client beans will be automatically configured and available for injection:
    +
    +[source,java]
    +----
    +@Autowired
    +private List mcpSyncClients;  // For sync client
    +
    +// OR
    +
    +@Autowired
    +private List mcpAsyncClients;  // For async client
    +----
    +
    +== Transport Support
    +
    +The auto-configuration supports multiple transport types:
    +
    +* Standard I/O (Stdio)
    +* SSE HTTP
    +* SSE WebFlux (requires `spring-ai-starter-mcp-client-webflux`)
    +
    +At least one transport must be available for the clients to be created.
    +
    +== Integration with Spring AI
    +
    +The starter automatically configures tool callbacks that integrate with Spring AI's tool execution framework, allowing MCP tools to be used as part of AI interactions.
    +
    +== Lifecycle Management
    +
    +The auto-configuration includes proper lifecycle management:
    +
    +* Automatic initialization of clients (if enabled)
    +* Proper cleanup of resources when the application context is closed
    +* Management of multiple client instances
    +
    +== Best Practices
    +
    +1. Choose the appropriate client type based on your application's needs:
    +   * Use SYNC client for traditional applications
    +   * Use ASYNC client for reactive applications
    +
    +2. Configure appropriate timeout values based on your use case:
    +[source,yaml]
    +----
    +spring:
    +  ai:
    +    mcp:
    +      client:
    +        request-timeout: 30s
    +----
    +
    +3. Use customizers for advanced client configuration:
    +[source,java]
    +----
    +@Component
    +public class MyMcpClientCustomizer implements McpSyncClientCustomizer {
    +    @Override
    +    public void customize(String name, McpClient.SyncSpec clientSpec) {
    +        // Custom configuration
    +    }
    +}
    +----
    +
    +== Troubleshooting
    +
    +Common issues and solutions:
    +
    +1. *Client Not Created*
    +   * Verify that at least one transport is available
    +   * Check if the client is enabled in configuration
    +   * Ensure required dependencies are present
    +
    +2. *Timeout Issues*
    +   * Adjust the `request-timeout` property
    +   * Check network connectivity
    +   * Verify server response times
    +
    +3. *Integration Issues*
    +   * Ensure proper transport configuration
    +   * Check client initialization status
    +
    +== Additional Resources
    +
    +* link:https://docs.spring.io/spring-ai/reference/[Spring AI Documentation]
    +* link:https://modelcontextprotocol.github.io/specification/[Model Context Protocol Specification]
    +* link:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration[Spring Boot Auto-configuration]
    diff --git a/mcp-client-boot-starter-docs.md b/mcp-client-boot-starter-docs.md
    new file mode 100644
    index 00000000000..ea7cc8600cc
    --- /dev/null
    +++ b/mcp-client-boot-starter-docs.md
    @@ -0,0 +1,284 @@
    +# Spring AI MCP Client Boot Starter
    +
    +The Spring AI MCP (Model Context Protocol) Client Boot Starter provides auto-configuration for MCP client functionality in Spring Boot applications. It supports both synchronous and asynchronous client implementations with various transport options.
    +
    +The MCP Client Boot Starter provides:
    +
    +- Automatic client initialization (if enabled)
    +- Support for multiple named transports
    +- Integration with Spring AI's tool execution framework
    +- Proper lifecycle management with automatic cleanup
    +- Customizable client creation through customizers
    +
    +
    +## Dependencies
    +
    +### Core Starter
    +
    +```xml
    +
    +    org.springframework.ai
    +    spring-ai-mcp-client-spring-boot-starter
    +    ${spring-ai.version}
    +
    +```
    +
    +It will connect, simultaneously, to one or more MCP Servers over `STDIO` (in-process) and/or `SSE` (remote) transports.
    +The SSE connection uses the HttpClient-based transport implementation.
    +Every connection to an MCP Server creates a new MCP Client instance.
    +You can opt for either `SYNC` or `ASYNC` MCP Clients (Note: you cannot mix sync and async clients).
    +For more enterprise-ready deployment, it is recommended to use the WebFlux-based SSE connection using the `spring-ai-mcp-client-webflux-spring-boot-starter` starter.
    +
    +### WebFlux Starter
    +
    +Similar to the core starter, it allows configuring one or more STDIO and SSE connections, but uses WebFlux-based SSE transport implementation.
    +
    +```xml
    +
    +    org.springframework.ai
    +    spring-ai-mcp-client-webflux-spring-boot-starter
    +    ${spring-ai.version}
    +
    +```
    +
    +## Configuration Properties
    +
    +### Common Properties
    +
    +All common configuration properties are prefixed with `spring.ai.mcp.client`:
    +
    +| Property | Description | Default Value |
    +|----------|-------------|---------------|
    +| `enabled` | Enable/disable the MCP client | `true` |
    +| `name` | Name of the MCP client instance (used for compatibility checks) | `spring-ai-mcp-client` |
    +| `version` | Version of the MCP client instance | `1.0.0` |
    +| `initialized` | Whether to initialize clients on creation | `true` |
    +| `request-timeout` | Timeout duration for MCP client requests | `20s` |
    +| `type` | Client type (SYNC or ASYNC). You can not mix client type. All clients can be either sync or async | `SYNC` |
    +| `root-change-notification` | Enable/disable root change notifications for all clients| `true` |
    +
    +### SSE Transport Properties
    +
    +Properties for Server-Sent Events (SSE) transport are prefixed with `spring.ai.mcp.client.sse`:
    +
    +| Property | Description |
    +|----------|-------------|
    +| `connections` | Map of named SSE connection configurations |
    +| `connections.[name].url` | URL endpoint for SSE communication with the MCP server |
    +
    +Example configuration:
    +```yaml
    +spring:
    +  ai:
    +    mcp:
    +      client:
    +        sse:
    +          connections:
    +            server1:
    +              url: http://localhost:8080
    +            server2:
    +              url: http://otherserver:8081
    +```
    +
    +### Stdio Transport Properties
    +
    +Properties for Standard I/O transport are prefixed with `spring.ai.mcp.client.stdio`:
    +
    +| Property | Description | Default Value |
    +|----------|-------------|---------------|
    +| `servers-configuration` | Resource containing the MCP servers configuration in JSON format | - |
    +| `connections` | Map of named stdio connection configurations | - |
    +| `connections.[name].command` | The command to execute for the MCP server | - |
    +| `connections.[name].args` | List of command arguments | - |
    +| `connections.[name].env` | Map of environment variables for the server process | - |
    +
    +Example configuration:
    +```yaml
    +spring:
    +  ai:
    +    mcp:
    +      client:
    +        stdio:
    +          root-change-notification: true
    +          connections:
    +            server1:
    +              command: /path/to/server
    +              args:
    +                - --port=8080
    +                - --mode=production
    +              env:
    +                API_KEY: your-api-key
    +                DEBUG: "true"
    +```
    +
    +Alternatively, you can configure stdio connections using an external JSON file using the [Claude Desctop format](https://modelcontextprotocol.io/quickstart/user):
    +
    +```yaml
    +spring:
    +  ai:
    +    mcp:
    +      client:
    +        stdio:
    +          servers-configuration: classpath:mcp-servers.json
    +```
    +
    +The Claude Destop format looks like this:
    +
    +```json
    +{
    +  "mcpServers": {
    +    "filesystem": {
    +      "command": "npx",
    +      "args": [
    +        "-y",
    +        "@modelcontextprotocol/server-filesystem",
    +        "/Users/username/Desktop",
    +        "/Users/username/Downloads"
    +      ]
    +    }
    +  }
    +}
    +```
    +Currently the Claude Destop supports only STDIO connection types.
    +
    +## Features
    +
    +### Client Types
    +
    +The starter supports two types of clients:
    +
    +1. **Synchronous Client (SYNC)**
    +   - Default client type
    +   - Blocking operations
    +   - Suitable for traditional request-response patterns
    +
    +2. **Asynchronous Client (ASYNC)**
    +   - Non-blocking operations
    +   - Suitable for reactive applications
    +   - Must be explicitly configured using `spring.ai.mcp.client.type=ASYNC`
    +
    +### Client Customization
    +
    +The auto-configuration supports customization through:
    +
    +- `McpSyncClientCustomizer` for synchronous clients
    +- `McpAsyncClientCustomizer` for asynchronous clients
    +
    +## Usage Example
    +
    +1. Add the appropriate starter dependency to your project.
    +
    +2. Configure the client in `application.properties` or `application.yml`:
    +
    +```yaml
    +spring:
    +  ai:
    +    mcp:
    +      client:
    +        enabled: true
    +        name: my-mcp-client
    +        version: 1.0.0
    +        request-timeout: 30s
    +        type: SYNC  # or ASYNC for reactive applications
    +        sse:
    +          connections:
    +            server1:
    +              url: http://localhost:8080
    +            server2:
    +              url: http://otherserver:8081        
    +        stdio:
    +          root-change-notification: false
    +          connections:
    +            server1:
    +              command: /path/to/server
    +              args:
    +                - --port=8080
    +                - --mode=production
    +              env:
    +                API_KEY: your-api-key
    +                DEBUG: "true"
    +```
    +
    +3. The MCP client beans will be automatically configured and available for injection:
    +
    +```java
    +@Autowired
    +private List mcpSyncClients;  // For sync client
    +
    +// OR
    +
    +@Autowired
    +private List mcpAsyncClients;  // For async client
    +```
    +
    +## Transport Support
    +
    +The auto-configuration supports multiple transport types:
    +
    +- Standard I/O (Stdio)
    +- SSE HTTP
    +- SSE WebFlux (requires `spring-ai-starter-mcp-client-webflux`)
    +
    +At least one transport must be available for the clients to be created.
    +
    +## Integration with Spring AI
    +
    +The starter automatically configures tool callbacks that integrate with Spring AI's tool execution framework, allowing MCP tools to be used as part of AI interactions.
    +
    +## Lifecycle Management
    +
    +The auto-configuration includes proper lifecycle management:
    +
    +- Automatic initialization of clients (if enabled)
    +- Proper cleanup of resources when the application context is closed
    +- Management of multiple client instances
    +
    +## Best Practices
    +
    +1. Choose the appropriate client type based on your application's needs:
    +   - Use SYNC client for traditional applications
    +   - Use ASYNC client for reactive applications
    +
    +2. Configure appropriate timeout values based on your use case:
    +   ```yaml
    +   spring:
    +     ai:
    +       mcp:
    +         client:
    +           request-timeout: 30s
    +   ```
    +
    +3. Use customizers for advanced client configuration:
    +   ```java
    +   @Component
    +   public class MyMcpClientCustomizer implements McpSyncClientCustomizer {
    +       @Override
    +       public void customize(String name, McpClient.SyncSpec clientSpec) {
    +           // Custom configuration
    +       }
    +   }
    +   ```
    +
    +## Troubleshooting
    +
    +Common issues and solutions:
    +
    +1. **Client Not Created**
    +   - Verify that at least one transport is available
    +   - Check if the client is enabled in configuration
    +   - Ensure required dependencies are present
    +
    +2. **Timeout Issues**
    +   - Adjust the `request-timeout` property
    +   - Check network connectivity
    +   - Verify server response times
    +
    +3. **Integration Issues**
    +   - Ensure proper transport configuration
    +   - Check client initialization status
    +
    +## Additional Resources
    +
    +- [Spring AI Documentation](https://docs.spring.io/spring-ai/reference/)
    +- [Model Context Protocol Specification](https://modelcontextprotocol.github.io/specification/)
    +- [Spring Boot Auto-configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration)
    diff --git a/mcp-helpers.adoc b/mcp-helpers.adoc
    new file mode 100644
    index 00000000000..532816eb1a3
    --- /dev/null
    +++ b/mcp-helpers.adoc
    @@ -0,0 +1,168 @@
    += Spring AI MCP Common Utilities
    +:page-title: Spring AI MCP Common Utilities
    +
    +This document provides reference documentation for the common utilities in Spring AI's Model Context Protocol (MCP) integration.
    +
    +== Overview
    +
    +The MCP common utilities provide foundational support for integrating Model Context Protocol with Spring AI applications. These utilities enable seamless communication between Spring AI's tool system and MCP servers, supporting both synchronous and asynchronous operations.
    +
    +== Core Components
    +
    +=== Tool Callbacks
    +
    +==== AsyncMcpToolCallback
    +
    +Adapts MCP tools to Spring AI's tool interface with asynchronous execution support.
    +
    +[source,java]
    +----
    +McpAsyncClient mcpClient = // obtain MCP client
    +Tool mcpTool = // obtain MCP tool definition
    +ToolCallback callback = new AsyncMcpToolCallback(mcpClient, mcpTool);
    +
    +// Use the tool through Spring AI's interfaces
    +ToolDefinition definition = callback.getToolDefinition();
    +String result = callback.call("{\"param\": \"value\"}");
    +----
    +
    +==== SyncMcpToolCallback
    +
    +Similar to AsyncMcpToolCallback but provides synchronous execution semantics.
    +
    +[source,java]
    +----
    +McpSyncClient mcpClient = // obtain MCP client
    +Tool mcpTool = // obtain MCP tool definition
    +ToolCallback callback = new SyncMcpToolCallback(mcpClient, mcpTool);
    +
    +// Use the tool through Spring AI's interfaces
    +ToolDefinition definition = callback.getToolDefinition();
    +String result = callback.call("{\"param\": \"value\"}");
    +----
    +
    +=== Tool Callback Providers
    +
    +==== AsyncMcpToolCallbackProvider
    +
    +Discovers and provides MCP tools asynchronously.
    +
    +[source,java]
    +----
    +McpAsyncClient mcpClient = // obtain MCP client
    +ToolCallbackProvider provider = new AsyncMcpToolCallbackProvider(mcpClient);
    +
    +// Get all available tools
    +ToolCallback[] tools = provider.getToolCallbacks();
    +----
    +
    +The provider also offers a utility method for working with multiple clients:
    +
    +[source,java]
    +----
    +List clients = // obtain list of clients
    +Flux callbacks = AsyncMcpToolCallbackProvider.asyncToolCallbacks(clients);
    +----
    +
    +==== SyncMcpToolCallbackProvider
    +
    +Similar to AsyncMcpToolCallbackProvider but works with synchronous clients.
    +
    +[source,java]
    +----
    +McpSyncClient mcpClient = // obtain MCP client
    +ToolCallbackProvider provider = new SyncMcpToolCallbackProvider(mcpClient);
    +
    +// Get all available tools
    +ToolCallback[] tools = provider.getToolCallbacks();
    +----
    +
    +For multiple clients:
    +
    +[source,java]
    +----
    +List clients = // obtain list of clients
    +List callbacks = SyncMcpToolCallbackProvider.syncToolCallbacks(clients);
    +----
    +
    +=== Client Customization
    +
    +==== McpAsyncClientCustomizer
    +
    +Allows customization of asynchronous MCP client configurations.
    +
    +[source,java]
    +----
    +@Component
    +public class CustomMcpAsyncClientCustomizer implements McpAsyncClientCustomizer {
    +    @Override
    +    public void customize(String name, McpClient.AsyncSpec spec) {
    +        // Customize the async client configuration
    +        spec.requestTimeout(Duration.ofSeconds(30));
    +    }
    +}
    +----
    +
    +==== McpSyncClientCustomizer
    +
    +Similar to McpAsyncClientCustomizer but for synchronous clients.
    +
    +[source,java]
    +----
    +@Component
    +public class CustomMcpSyncClientCustomizer implements McpSyncClientCustomizer {
    +    @Override
    +    public void customize(String name, McpClient.SyncSpec spec) {
    +        // Customize the sync client configuration
    +        spec.requestTimeout(Duration.ofSeconds(30));
    +    }
    +}
    +----
    +
    +=== Utility Classes
    +
    +==== McpToolUtils
    +
    +Provides helper methods for working with MCP tools in a Spring AI environment.
    +
    +Converting Spring AI tool callbacks to MCP tool registrations:
    +
    +[source,java]
    +----
    +// For synchronous tools
    +List toolCallbacks = // obtain tool callbacks
    +List syncRegs = McpToolUtils.toSyncToolRegistration(toolCallbacks);
    +
    +// For asynchronous tools
    +List asyncRegs = McpToolUtils.toAsyncToolRegistration(toolCallbacks);
    +----
    +
    +Getting tool callbacks from MCP clients:
    +
    +[source,java]
    +----
    +// From sync clients
    +List syncClients = // obtain sync clients
    +List syncCallbacks = McpToolUtils.getToolCallbacksFromSyncClients(syncClients);
    +
    +// From async clients
    +List asyncClients = // obtain async clients
    +List asyncCallbacks = McpToolUtils.getToolCallbacksFromAsyncClients(asyncClients);
    +----
    +
    +=== Native Image Support
    +
    +==== McpHints
    +
    +Provides GraalVM native image hints for MCP schema classes.
    +
    +[source,java]
    +----
    +@Configuration
    +@ImportRuntimeHints(McpHints.class)
    +public class MyConfiguration {
    +    // Configuration code
    +}
    +----
    +
    +This class automatically registers all necessary reflection hints for MCP schema classes when building native images.
    diff --git a/mcp-helpers.md b/mcp-helpers.md
    new file mode 100644
    index 00000000000..90b643e2d7f
    --- /dev/null
    +++ b/mcp-helpers.md
    @@ -0,0 +1,156 @@
    +# Spring AI MCP Common Utilities
    +
    +This document provides reference documentation for the common utilities in Spring AI's Model Context Protocol (MCP) integration.
    +
    +## Overview
    +
    +The MCP common utilities provide foundational support for integrating Model Context Protocol with Spring AI applications. These utilities enable seamless communication between Spring AI's tool system and MCP servers, supporting both synchronous and asynchronous operations.
    +
    +## Core Components
    +
    +### Tool Callbacks
    +
    +#### AsyncMcpToolCallback
    +
    +Adapts MCP tools to Spring AI's tool interface with asynchronous execution support.
    +
    +```java
    +McpAsyncClient mcpClient = // obtain MCP client
    +Tool mcpTool = // obtain MCP tool definition
    +ToolCallback callback = new AsyncMcpToolCallback(mcpClient, mcpTool);
    +
    +// Use the tool through Spring AI's interfaces
    +ToolDefinition definition = callback.getToolDefinition();
    +String result = callback.call("{\"param\": \"value\"}");
    +```
    +
    +#### SyncMcpToolCallback
    +
    +Similar to AsyncMcpToolCallback but provides synchronous execution semantics.
    +
    +```java
    +McpSyncClient mcpClient = // obtain MCP client
    +Tool mcpTool = // obtain MCP tool definition
    +ToolCallback callback = new SyncMcpToolCallback(mcpClient, mcpTool);
    +
    +// Use the tool through Spring AI's interfaces
    +ToolDefinition definition = callback.getToolDefinition();
    +String result = callback.call("{\"param\": \"value\"}");
    +```
    +
    +### Tool Callback Providers
    +
    +#### AsyncMcpToolCallbackProvider
    +
    +Discovers and provides MCP tools asynchronously.
    +
    +```java
    +McpAsyncClient mcpClient = // obtain MCP client
    +ToolCallbackProvider provider = new AsyncMcpToolCallbackProvider(mcpClient);
    +
    +// Get all available tools
    +ToolCallback[] tools = provider.getToolCallbacks();
    +```
    +
    +The provider also offers a utility method for working with multiple clients:
    +
    +```java
    +List clients = // obtain list of clients
    +Flux callbacks = AsyncMcpToolCallbackProvider.asyncToolCallbacks(clients);
    +```
    +
    +#### SyncMcpToolCallbackProvider
    +
    +Similar to AsyncMcpToolCallbackProvider but works with synchronous clients.
    +
    +```java
    +McpSyncClient mcpClient = // obtain MCP client
    +ToolCallbackProvider provider = new SyncMcpToolCallbackProvider(mcpClient);
    +
    +// Get all available tools
    +ToolCallback[] tools = provider.getToolCallbacks();
    +```
    +
    +For multiple clients:
    +
    +```java
    +List clients = // obtain list of clients
    +List callbacks = SyncMcpToolCallbackProvider.syncToolCallbacks(clients);
    +```
    +
    +### Client Customization
    +
    +#### McpAsyncClientCustomizer
    +
    +Allows customization of asynchronous MCP client configurations.
    +
    +```java
    +@Component
    +public class CustomMcpAsyncClientCustomizer implements McpAsyncClientCustomizer {
    +    @Override
    +    public void customize(String name, McpClient.AsyncSpec spec) {
    +        // Customize the async client configuration
    +        spec.requestTimeout(Duration.ofSeconds(30));
    +    }
    +}
    +```
    +
    +#### McpSyncClientCustomizer
    +
    +Similar to McpAsyncClientCustomizer but for synchronous clients.
    +
    +```java
    +@Component
    +public class CustomMcpSyncClientCustomizer implements McpSyncClientCustomizer {
    +    @Override
    +    public void customize(String name, McpClient.SyncSpec spec) {
    +        // Customize the sync client configuration
    +        spec.requestTimeout(Duration.ofSeconds(30));
    +    }
    +}
    +```
    +
    +### Utility Classes
    +
    +#### McpToolUtils
    +
    +Provides helper methods for working with MCP tools in a Spring AI environment.
    +
    +Converting Spring AI tool callbacks to MCP tool registrations:
    +
    +```java
    +// For synchronous tools
    +List toolCallbacks = // obtain tool callbacks
    +List syncRegs = McpToolUtils.toSyncToolRegistration(toolCallbacks);
    +
    +// For asynchronous tools
    +List asyncRegs = McpToolUtils.toAsyncToolRegistration(toolCallbacks);
    +```
    +
    +Getting tool callbacks from MCP clients:
    +
    +```java
    +// From sync clients
    +List syncClients = // obtain sync clients
    +List syncCallbacks = McpToolUtils.getToolCallbacksFromSyncClients(syncClients);
    +
    +// From async clients
    +List asyncClients = // obtain async clients
    +List asyncCallbacks = McpToolUtils.getToolCallbacksFromAsyncClients(asyncClients);
    +```
    +
    +### Native Image Support
    +
    +#### McpHints
    +
    +Provides GraalVM native image hints for MCP schema classes.
    +
    +```java
    +@Configuration
    +@ImportRuntimeHints(McpHints.class)
    +public class MyConfiguration {
    +    // Configuration code
    +}
    +```
    +
    +This class automatically registers all necessary reflection hints for MCP schema classes when building native images.
    \ No newline at end of file
    diff --git a/mcp-server-boot-starter-docs.adoc b/mcp-server-boot-starter-docs.adoc
    new file mode 100644
    index 00000000000..cf9784e9da1
    --- /dev/null
    +++ b/mcp-server-boot-starter-docs.adoc
    @@ -0,0 +1,310 @@
    += Spring AI MCP Server Boot Starter
    +
    +The Spring AI MCP (Model Context Protocol) Server Boot Starter provides auto-configuration for setting up an MCP server in Spring Boot applications. It enables seamless integration of MCP server capabilities with Spring Boot's auto-configuration system.
    +
    +== Overview
    +
    +The MCP Server Boot Starter offers:
    +
    +* Automatic configuration of MCP server components
    +* Support for both synchronous and asynchronous operation modes
    +* Multiple transport layer options
    +* Flexible tool, resource, and prompt registration
    +* Change notification capabilities
    +
    +== Starter Dependencies
    +
    +Choose one of the following starters based on your transport requirements:
    +
    +=== 1. Standard MCP Server
    +
    +Full MCP Server features support with `STDIO` server transport.
    +
    +[source,xml]
    +----
    +
    +    org.springframework.ai
    +    spring-ai-mcp-server-spring-boot-starter
    +    ${spring-ai.version}
    +
    +----
    +
    +=== 2. WebMVC Server
    +
    +Full MCP Server features support with `SSE` (Server-Sent Events) server transport based on Spring MVC and an optional `STDIO` transport.
    +
    +[source,xml]
    +----
    +
    +    org.springframework.ai
    +    spring-ai-mcp-server-webmvc-spring-boot-starter
    +    ${spring-ai.version}
    +
    +----
    +
    +This starter includes:
    +
    +* spring-boot-starter-web
    +* mcp-spring-webmvc
    +(optionally allows `stdio` transport deployment)
    +
    +=== 3. WebFlux Server
    +
    +Full MCP Server features support with `SSE` (Server-Sent Events) server transport based on Spring WebFlux and an optional `STDIO` transport.
    +
    +[source,xml]
    +----
    +
    +    org.springframework.ai
    +    spring-ai-mcp-server-webflux-spring-boot-starter
    +    ${spring-ai.version}
    +
    +----
    +
    +This starter includes:
    +
    +* spring-boot-starter-webflux
    +* mcp-spring-webflux
    +(optionally allows `stdio` transport deployment)
    +
    +== Configuration Properties
    +
    +All properties are prefixed with `spring.ai.mcp.server`:
    +
    +[options="header"]
    +|===
    +|Property |Description |Default
    +|`enabled` |Enable/disable the MCP server |`true`
    +|`stdio` |Enable/disable stdio transport |`false`
    +|`name` |Server name for identification |`mcp-server`
    +|`version` |Server version |`1.0.0`
    +|`type` |Server type (SYNC/ASYNC) |`SYNC`
    +|`resource-change-notification` |Enable resource change notifications |`true`
    +|`tool-change-notification` |Enable tool change notifications |`true`
    +|`prompt-change-notification` |Enable prompt change notifications |`true`
    +|`sse-message-endpoint` |SSE endpoint path for web transport |`/mcp/message`
    +|===
    +
    +== Server Types
    +
    +=== Synchronous Server
    +* Default server type
    +* Uses `McpSyncServer`
    +* Suitable for straightforward request-response patterns
    +* Configure with `spring.ai.mcp.server.type=SYNC`
    +* Automatically configures synchronous tool registrations
    +
    +=== Asynchronous Server
    +* Uses `McpAsyncServer`
    +* Suitable for non-blocking operations
    +* Configure with `spring.ai.mcp.server.type=ASYNC`
    +* Automatically configures asynchronous tool registrations with Project Reactor support
    +
    +== Transport Options
    +
    +The MCP Server supports three transport mechanisms, each with its dedicated starter:
    +
    +=== 1. Standard Input/Output (STDIO)
    +* Use `spring-ai-mcp-server-spring-boot-starter`
    +* Default transport when using the standard starter
    +* Suitable for command-line tools and testing
    +* No additional web dependencies required
    +
    +=== 2. Spring MVC (Server-Sent Events)
    +* Use `spring-ai-mcp-server-webmvc-spring-boot-starter`
    +* Provides HTTP-based transport using Spring MVC
    +* Uses `WebMvcSseServerTransport`
    +* Automatically configures SSE endpoints
    +* Ideal for traditional web applications
    +* Optionally you can deploy `STDIO` transport by setting the `spring.ai.mcp.server.stdio=true` property.
    +
    +=== 3. Spring WebFlux (Reactive SSE)
    +* Use `spring-ai-mcp-server-webflux-spring-boot-starter`
    +* Provides reactive transport using Spring WebFlux
    +* Uses `WebFluxSseServerTransport`
    +* Automatically configures reactive SSE endpoints
    +* Ideal for reactive applications with non-blocking requirements
    +* Optionally you can deploy `STDIO` transport by setting the `spring.ai.mcp.server.stdio=true` property.
    +
    +== Features and Capabilities
    +
    +=== 1. Tools Registration
    +* Support for both sync and async tool execution
    +* Automatic tool registration through Spring beans
    +* Change notification support
    +* Tools are automatically converted to sync/async registrations based on server type
    +
    +=== 2. Resource Management
    +* Static and dynamic resource registration
    +* Optional change notifications
    +* Support for resource templates
    +* Automatic conversion between sync/async resource registrations
    +
    +=== 3. Prompt Templates
    +* Configurable prompt registration
    +* Change notification support
    +* Template versioning
    +* Automatic conversion between sync/async prompt registrations
    +
    +=== 4. Root Change Consumers
    +* Support for monitoring root changes
    +* Automatic conversion to async consumers for reactive applications
    +* Optional registration through Spring beans
    +
    +== Usage Examples
    +
    +=== 1. Standard STDIO Server Configuration
    +[source,yaml]
    +----
    +# Using spring-ai-mcp-server-spring-boot-starter
    +spring:
    +  ai:
    +    mcp:
    +      server:
    +        name: stdio-mcp-server
    +        version: 1.0.0
    +        type: SYNC
    +        stdio: true
    +----
    +
    +=== 2. WebMVC Server Configuration
    +[source,yaml]
    +----
    +# Using spring-ai-mcp-server-webmvc-spring-boot-starter
    +spring:
    +  ai:
    +    mcp:
    +      server:
    +        name: webmvc-mcp-server
    +        version: 1.0.0
    +        type: SYNC
    +        stdio: false
    +        sse-message-endpoint: /mcp/messages
    +----
    +
    +=== 3. WebFlux Server Configuration
    +[source,yaml]
    +----
    +# Using spring-ai-mcp-server-webflux-spring-boot-starter
    +spring:
    +  ai:
    +    mcp:
    +      server:
    +        name: webflux-mcp-server
    +        version: 1.0.0
    +        type: ASYNC  # Recommended for reactive applications
    +        stdio: false
    +        sse-message-endpoint: /mcp/messages
    +----
    +
    +=== Tool Registration Examples
    +
    +==== 1. Synchronous Tool (for SYNC server type)
    +[source,java]
    +----
    +@Configuration
    +public class SyncToolConfig {
    +    
    +    @Bean
    +    public ToolCallback syncTool() {
    +        return new ToolCallback() {
    +            @Override
    +            public String getName() {
    +                return "syncTool";
    +            }
    +            
    +            @Override
    +            public Object execute(Map params) {
    +                // Synchronous implementation
    +                return result;
    +            }
    +        };
    +    }
    +}
    +----
    +
    +==== 2. Asynchronous Tool (for ASYNC server type)
    +[source,java]
    +----
    +@Configuration
    +public class AsyncToolConfig {
    +    
    +    @Bean
    +    public ToolCallback asyncTool() {
    +        return new ToolCallback() {
    +            @Override
    +            public String getName() {
    +                return "asyncTool";
    +            }
    +            
    +            @Override
    +            public Object execute(Map params) {
    +                // Asynchronous implementation using Project Reactor
    +                return Mono.just("result")
    +                    .map(r -> processResult(r))
    +                    .subscribeOn(Schedulers.boundedElastic());
    +            }
    +        };
    +    }
    +}
    +----
    +
    +== Auto-configuration Classes
    +
    +The starter provides several auto-configuration classes:
    +
    +1. `MpcServerAutoConfiguration`: Core server configuration
    +* Configures basic server components
    +* Handles tool, resource, and prompt registrations
    +* Manages server capabilities and change notifications
    +* Provides both sync and async server implementations
    +
    +2. `MpcWebMvcServerAutoConfiguration`: Spring MVC transport
    +* Configures SSE endpoints for web transport
    +* Integrates with Spring MVC infrastructure
    +
    +3. `MpcWebFluxServerAutoConfiguration`: Spring WebFlux transport
    +* Configures reactive SSE endpoints
    +* Integrates with Spring WebFlux infrastructure
    +
    +These classes are conditionally enabled based on the classpath and configuration properties.
    +
    +== Conditional Configuration
    +
    +The auto-configuration is activated when:
    +
    +* Required MCP classes are on the classpath
    +* `spring.ai.mcp.server.enabled=true` (default)
    +* Appropriate transport dependencies are available
    +
    +== Best Practices
    +
    +1. Choose the appropriate server type based on your use case:
    +* Use SYNC for simple request-response patterns
    +* Use ASYNC for non-blocking operations and reactive applications
    +
    +2. Select the transport mechanism based on your application type:
    +* Use STDIO for command-line tools and testing
    +* Use WebMvc for traditional web applications
    +* Use WebFlux for reactive applications
    +
    +3. Configure change notifications based on your needs:
    +* Enable only the notifications you need
    +* Consider performance implications of notifications
    +* Use appropriate consumers for root changes
    +
    +4. Properly version your server and tools:
    +* Use semantic versioning
    +* Document version changes
    +* Handle version compatibility
    +
    +5. Tool Implementation:
    +* Implement tools as Spring beans for automatic registration
    +* Return Mono/Flux for async operations in ASYNC mode
    +* Use appropriate error handling strategies
    +
    +== Additional Resources
    +
    +* link:https://docs.spring.io/spring-ai/reference/[Spring AI Documentation]
    +* link:https://modelcontextprotocol.github.io/specification/[Model Context Protocol Specification]
    +* link:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration[Spring Boot Auto-configuration]
    diff --git a/mcp-server-boot-starter-docs.md b/mcp-server-boot-starter-docs.md
    new file mode 100644
    index 00000000000..393c6935ea5
    --- /dev/null
    +++ b/mcp-server-boot-starter-docs.md
    @@ -0,0 +1,294 @@
    +# Spring AI MCP Server Boot Starter
    +
    +The Spring AI MCP (Model Context Protocol) Server Boot Starter provides auto-configuration for setting up an MCP server in Spring Boot applications. It enables seamless integration of MCP server capabilities with Spring Boot's auto-configuration system.
    +
    +## Overview
    +
    +The MCP Server Boot Starter offers:
    +- Automatic configuration of MCP server components
    +- Support for both synchronous and asynchronous operation modes
    +- Multiple transport layer options
    +- Flexible tool, resource, and prompt registration
    +- Change notification capabilities
    +
    +## Starter Dependencies
    +
    +Choose one of the following starters based on your transport requirements:
    +
    +### 1. Standard MCP Server
    +
    +Full MCP Server features support with `STDIO` server transport.
    +
    +```xml
    +
    +    org.springframework.ai
    +    spring-ai-mcp-server-spring-boot-starter
    +    ${spring-ai.version}
    +
    +```
    +
    +### 2. WebMVC Server
    +
    +Full MCP Server features support with `SSE` (Server-Sent Events) server transport based on Spring MVC and an optional `STDIO` transport.
    +
    +```xml
    +
    +    org.springframework.ai
    +    spring-ai-mcp-server-webmvc-spring-boot-starter
    +    ${spring-ai.version}
    +
    +```
    +This starter includes:
    +- spring-boot-starter-web
    +- mcp-spring-webmvc
    +(optionally allows `stdio` transport deployment)
    +
    +### 3. WebFlux Server 
    +
    +Full MCP Server features support with `SSE` (Server-Sent Events) server transport based on Spring WebFlux and an optional `STDIO` transport.
    +
    +```xml
    +
    +    org.springframework.ai
    +    spring-ai-mcp-server-webflux-spring-boot-starter
    +    ${spring-ai.version}
    +
    +```
    +This starter includes:
    +- spring-boot-starter-webflux
    +- mcp-spring-webflux
    +(optionally allows `stdio` transport deployment)
    +
    +## Configuration Properties
    +
    +All properties are prefixed with `spring.ai.mcp.server`:
    +
    +| Property | Description | Default |
    +|----------|-------------|---------|
    +| `enabled` | Enable/disable the MCP server | `true` |
    +| `stdio` | Enable/disable stdio transport | `false` |
    +| `name` | Server name for identification | `mcp-server` |
    +| `version` | Server version | `1.0.0` |
    +| `type` | Server type (SYNC/ASYNC) | `SYNC` |
    +| `resource-change-notification` | Enable resource change notifications | `true` |
    +| `tool-change-notification` | Enable tool change notifications | `true` |
    +| `prompt-change-notification` | Enable prompt change notifications | `true` |
    +| `sse-message-endpoint` | SSE endpoint path for web transport | `/mcp/message` |
    +
    +## Server Types
    +
    +### Synchronous Server
    +- Default server type
    +- Uses `McpSyncServer`
    +- Suitable for straightforward request-response patterns
    +- Configure with `spring.ai.mcp.server.type=SYNC`
    +- Automatically configures synchronous tool registrations
    +
    +### Asynchronous Server
    +- Uses `McpAsyncServer`
    +- Suitable for non-blocking operations
    +- Configure with `spring.ai.mcp.server.type=ASYNC`
    +- Automatically configures asynchronous tool registrations with Project Reactor support
    +
    +## Transport Options
    +
    +The MCP Server supports three transport mechanisms, each with its dedicated starter:
    +
    +### 1. Standard Input/Output (STDIO)
    +- Use `spring-ai-mcp-server-spring-boot-starter`
    +- Default transport when using the standard starter
    +- Suitable for command-line tools and testing
    +- No additional web dependencies required
    +
    +### 2. Spring MVC (Server-Sent Events)
    +- Use `spring-ai-mcp-server-webmvc-spring-boot-starter`
    +- Provides HTTP-based transport using Spring MVC
    +- Uses `WebMvcSseServerTransport`
    +- Automatically configures SSE endpoints
    +- Ideal for traditional web applications
    +- Optionally you can deploy `STDIO` transport by setting the `spring.ai.mcp.server.stdio=true` property.
    +
    +### 3. Spring WebFlux (Reactive SSE)
    +- Use `spring-ai-mcp-server-webflux-spring-boot-starter`
    +- Provides reactive transport using Spring WebFlux
    +- Uses `WebFluxSseServerTransport`
    +- Automatically configures reactive SSE endpoints
    +- Ideal for reactive applications with non-blocking requirements
    +- Optionally you can deploy `STDIO` transport by setting the `spring.ai.mcp.server.stdio=true` property.
    +
    +## Features and Capabilities
    +
    +### 1. Tools Registration
    +- Support for both sync and async tool execution
    +- Automatic tool registration through Spring beans
    +- Change notification support
    +- Tools are automatically converted to sync/async registrations based on server type
    +
    +### 2. Resource Management
    +- Static and dynamic resource registration
    +- Optional change notifications
    +- Support for resource templates
    +- Automatic conversion between sync/async resource registrations
    +
    +### 3. Prompt Templates
    +- Configurable prompt registration
    +- Change notification support
    +- Template versioning
    +- Automatic conversion between sync/async prompt registrations
    +
    +### 4. Root Change Consumers
    +- Support for monitoring root changes
    +- Automatic conversion to async consumers for reactive applications
    +- Optional registration through Spring beans
    +
    +## Usage Examples
    +
    +### 1. Standard STDIO Server Configuration
    +```yaml
    +# Using spring-ai-mcp-server-spring-boot-starter
    +spring:
    +  ai:
    +    mcp:
    +      server:
    +        name: stdio-mcp-server
    +        version: 1.0.0
    +        type: SYNC
    +        stdio: true
    +```
    +
    +### 2. WebMVC Server Configuration
    +```yaml
    +# Using spring-ai-mcp-server-webmvc-spring-boot-starter
    +spring:
    +  ai:
    +    mcp:
    +      server:
    +        name: webmvc-mcp-server
    +        version: 1.0.0
    +        type: SYNC
    +        stdio: false
    +        sse-message-endpoint: /mcp/messages
    +```
    +
    +### 3. WebFlux Server Configuration
    +```yaml
    +# Using spring-ai-mcp-server-webflux-spring-boot-starter
    +spring:
    +  ai:
    +    mcp:
    +      server:
    +        name: webflux-mcp-server
    +        version: 1.0.0
    +        type: ASYNC  # Recommended for reactive applications
    +        stdio: false
    +        sse-message-endpoint: /mcp/messages
    +```
    +
    +### Tool Registration Examples
    +
    +#### 1. Synchronous Tool (for SYNC server type)
    +```java
    +@Configuration
    +public class SyncToolConfig {
    +    
    +    @Bean
    +    public ToolCallback syncTool() {
    +        return new ToolCallback() {
    +            @Override
    +            public String getName() {
    +                return "syncTool";
    +            }
    +            
    +            @Override
    +            public Object execute(Map params) {
    +                // Synchronous implementation
    +                return result;
    +            }
    +        };
    +    }
    +}
    +```
    +
    +#### 2. Asynchronous Tool (for ASYNC server type)
    +```java
    +@Configuration
    +public class AsyncToolConfig {
    +    
    +    @Bean
    +    public ToolCallback asyncTool() {
    +        return new ToolCallback() {
    +            @Override
    +            public String getName() {
    +                return "asyncTool";
    +            }
    +            
    +            @Override
    +            public Object execute(Map params) {
    +                // Asynchronous implementation using Project Reactor
    +                return Mono.just("result")
    +                    .map(r -> processResult(r))
    +                    .subscribeOn(Schedulers.boundedElastic());
    +            }
    +        };
    +    }
    +}
    +```
    +
    +## Auto-configuration Classes
    +
    +The starter provides several auto-configuration classes:
    +
    +1. `MpcServerAutoConfiguration`: Core server configuration
    +   - Configures basic server components
    +   - Handles tool, resource, and prompt registrations
    +   - Manages server capabilities and change notifications
    +   - Provides both sync and async server implementations
    +
    +2. `MpcWebMvcServerAutoConfiguration`: Spring MVC transport
    +   - Configures SSE endpoints for web transport
    +   - Integrates with Spring MVC infrastructure
    +
    +3. `MpcWebFluxServerAutoConfiguration`: Spring WebFlux transport
    +   - Configures reactive SSE endpoints
    +   - Integrates with Spring WebFlux infrastructure
    +
    +These classes are conditionally enabled based on the classpath and configuration properties.
    +
    +## Conditional Configuration
    +
    +The auto-configuration is activated when:
    +- Required MCP classes are on the classpath
    +- `spring.ai.mcp.server.enabled=true` (default)
    +- Appropriate transport dependencies are available
    +
    +## Best Practices
    +
    +1. Choose the appropriate server type based on your use case:
    +   - Use SYNC for simple request-response patterns
    +   - Use ASYNC for non-blocking operations and reactive applications
    +
    +2. Select the transport mechanism based on your application type:
    +   - Use STDIO for command-line tools and testing
    +   - Use WebMvc for traditional web applications
    +   - Use WebFlux for reactive applications
    +
    +3. Configure change notifications based on your needs:
    +   - Enable only the notifications you need
    +   - Consider performance implications of notifications
    +   - Use appropriate consumers for root changes
    +
    +4. Properly version your server and tools:
    +   - Use semantic versioning
    +   - Document version changes
    +   - Handle version compatibility
    +
    +5. Tool Implementation:
    +   - Implement tools as Spring beans for automatic registration
    +   - Return Mono/Flux for async operations in ASYNC mode
    +   - Use appropriate error handling strategies
    +
    +## Additional Resources
    +
    +- [Spring AI Documentation](https://docs.spring.io/spring-ai/reference/)
    +- [Model Context Protocol Specification](https://modelcontextprotocol.github.io/specification/)
    +- [Spring Boot Auto-configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration)
    diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java b/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java
    new file mode 100644
    index 00000000000..121e1d9b867
    --- /dev/null
    +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java
    @@ -0,0 +1,113 @@
    +/*
    + * Copyright 2025-2025 the original author or authors.
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      https://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.springframework.ai.mcp;
    +
    +import java.util.Map;
    +
    +import io.modelcontextprotocol.client.McpAsyncClient;
    +import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
    +import io.modelcontextprotocol.spec.McpSchema.Tool;
    +
    +import org.springframework.ai.model.ModelOptionsUtils;
    +import org.springframework.ai.tool.ToolCallback;
    +import org.springframework.ai.tool.definition.ToolDefinition;
    +
    +/**
    + * Implementation of {@link ToolCallback} that adapts MCP tools to Spring AI's tool
    + * interface with asynchronous execution support.
    + * 

    + * This class acts as a bridge between the Model Context Protocol (MCP) and Spring AI's + * tool system, allowing MCP tools to be used seamlessly within Spring AI applications. + * It: + *

      + *
    • Converts MCP tool definitions to Spring AI tool definitions
    • + *
    • Handles the asynchronous execution of tool calls through the MCP client
    • + *
    • Manages JSON serialization/deserialization of tool inputs and outputs
    • + *
    + *

    + * Example usage:

    {@code
    + * McpAsyncClient mcpClient = // obtain MCP client
    + * Tool mcpTool = // obtain MCP tool definition
    + * ToolCallback callback = new AsyncMcpToolCallback(mcpClient, mcpTool);
    + *
    + * // Use the tool through Spring AI's interfaces
    + * ToolDefinition definition = callback.getToolDefinition();
    + * String result = callback.call("{\"param\": \"value\"}");
    + * }
    + * + * @author Christian Tzolov + * @see ToolCallback + * @see McpAsyncClient + * @see Tool + */ +public class AsyncMcpToolCallback implements ToolCallback { + + private final McpAsyncClient asyncMcpClient; + + private final Tool tool; + + /** + * Creates a new {@code AsyncMcpToolCallback} instance. + * @param mcpClient the MCP client to use for tool execution + * @param tool the MCP tool definition to adapt + */ + public AsyncMcpToolCallback(McpAsyncClient mcpClient, Tool tool) { + this.asyncMcpClient = mcpClient; + this.tool = tool; + } + + /** + * Returns a Spring AI tool definition adapted from the MCP tool. + *

    + * The tool definition includes: + *

      + *
    • The tool's name from the MCP definition
    • + *
    • The tool's description from the MCP definition
    • + *
    • The input schema converted to JSON format
    • + *
    + * @return the Spring AI tool definition + */ + @Override + public ToolDefinition getToolDefinition() { + return ToolDefinition.builder() + .name(this.tool.name()) + .description(this.tool.description()) + .inputSchema(ModelOptionsUtils.toJsonString(this.tool.inputSchema())) + .build(); + } + + /** + * Executes the tool with the provided input asynchronously. + *

    + * This method: + *

      + *
    1. Converts the JSON input string to a map of arguments
    2. + *
    3. Calls the tool through the MCP client asynchronously
    4. + *
    5. Converts the tool's response content to a JSON string
    6. + *
    + * @param functionInput the tool input as a JSON string + * @return the tool's response as a JSON string + */ + @Override + public String call(String functionInput) { + Map arguments = ModelOptionsUtils.jsonToMap(functionInput); + return this.asyncMcpClient.callTool(new CallToolRequest(this.getToolDefinition().name(), arguments)) + .map(response -> ModelOptionsUtils.toJsonString(response.content())) + .block(); + } + +} diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallbackProvider.java b/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallbackProvider.java new file mode 100644 index 00000000000..f8f1bb1ebc0 --- /dev/null +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallbackProvider.java @@ -0,0 +1,132 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.mcp; + +import java.util.List; + +import io.modelcontextprotocol.client.McpAsyncClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.ai.tool.util.ToolUtils; +import org.springframework.util.CollectionUtils; + +/** + * Implementation of {@link ToolCallbackProvider} that discovers and provides MCP tools + * asynchronously. + *

    + * This class acts as a tool provider for Spring AI, automatically discovering tools from + * an MCP server and making them available as Spring AI tools. It: + *

      + *
    • Connects to an MCP server through an async client
    • + *
    • Lists and retrieves available tools from the server
    • + *
    • Creates {@link AsyncMcpToolCallback} instances for each discovered tool
    • + *
    • Validates tool names to prevent duplicates
    • + *
    + *

    + * Example usage:

    {@code
    + * McpAsyncClient mcpClient = // obtain MCP client
    + * ToolCallbackProvider provider = new AsyncMcpToolCallbackProvider(mcpClient);
    + *
    + * // Get all available tools
    + * ToolCallback[] tools = provider.getToolCallbacks();
    + * }
    + * + * @author Christian Tzolov + * @since 1.0.0 + * @see ToolCallbackProvider + * @see AsyncMcpToolCallback + * @see McpAsyncClient + */ +public class AsyncMcpToolCallbackProvider implements ToolCallbackProvider { + + private final McpAsyncClient mcpClient; + + /** + * Creates a new {@code AsyncMcpToolCallbackProvider} instance. + * @param mcpClient the MCP client to use for discovering tools + */ + public AsyncMcpToolCallbackProvider(McpAsyncClient mcpClient) { + this.mcpClient = mcpClient; + } + + /** + * Discovers and returns all available tools from the MCP server asynchronously. + *

    + * This method: + *

      + *
    1. Retrieves the list of tools from the MCP server
    2. + *
    3. Creates a {@link AsyncMcpToolCallback} for each tool
    4. + *
    5. Validates that there are no duplicate tool names
    6. + *
    + * @return an array of tool callbacks, one for each discovered tool + * @throws IllegalStateException if duplicate tool names are found + */ + @Override + public ToolCallback[] getToolCallbacks() { + var toolCallbacks = this.mcpClient.listTools() + .map(response -> response.tools() + .stream() + .map(tool -> new AsyncMcpToolCallback(this.mcpClient, tool)) + .toArray(ToolCallback[]::new)) + .block(); + + validateToolCallbacks(toolCallbacks); + + return toolCallbacks; + } + + /** + * Validates that there are no duplicate tool names in the provided callbacks. + *

    + * This method ensures that each tool has a unique name, which is required for proper + * tool resolution and execution. + * @param toolCallbacks the tool callbacks to validate + * @throws IllegalStateException if duplicate tool names are found + */ + private void validateToolCallbacks(ToolCallback[] toolCallbacks) { + List duplicateToolNames = ToolUtils.getDuplicateToolNames(toolCallbacks); + if (!duplicateToolNames.isEmpty()) { + throw new IllegalStateException( + "Multiple tools with the same name (%s)".formatted(String.join(", ", duplicateToolNames))); + } + } + + /** + * Creates a reactive stream of tool callbacks from multiple MCP clients. + *

    + * This utility method: + *

      + *
    1. Takes a list of MCP clients
    2. + *
    3. Creates a provider for each client
    4. + *
    5. Retrieves and flattens all tool callbacks into a single stream
    6. + *
    + * @param mcpClients the list of MCP clients to create callbacks from + * @return a Flux of tool callbacks from all provided clients + */ + public static Flux asyncToolCallbacks(List mcpClients) { + if (CollectionUtils.isEmpty(mcpClients)) { + return Flux.empty(); + } + + return Flux.fromIterable(mcpClients) + .flatMap(mcpClient -> Mono.just(new AsyncMcpToolCallbackProvider(mcpClient).getToolCallbacks())) + .flatMap(callbacks -> Flux.fromArray(callbacks)); + } + +} diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/McpSyncClientCustomizer.java b/mcp/common/src/main/java/org/springframework/ai/mcp/McpSyncClientCustomizer.java deleted file mode 100644 index 0e1a7ae2441..00000000000 --- a/mcp/common/src/main/java/org/springframework/ai/mcp/McpSyncClientCustomizer.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2025-2025 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.ai.mcp; - -import io.modelcontextprotocol.client.McpClient; - -/** - * @author Christian Tzolov - * @since 1.0.0 - */ -public interface McpSyncClientCustomizer { - - void customize(String name, McpClient.SyncSpec sync); - -} diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java b/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java index 821ff153e01..5a69b92b8e5 100644 --- a/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java @@ -17,6 +17,7 @@ import java.util.List; +import io.modelcontextprotocol.client.McpAsyncClient; import io.modelcontextprotocol.client.McpSyncClient; import io.modelcontextprotocol.server.McpServerFeatures; import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolRegistration; @@ -180,11 +181,32 @@ public static McpServerFeatures.AsyncToolRegistration toAsyncToolRegistration(To .subscribeOn(Schedulers.boundedElastic())); } - public static List getToolCallbacks(McpSyncClient... mcpClients) { - return getToolCallbacks(List.of(mcpClients)); + /** + * Convenience method to get tool callbacks from multiple synchronous MCP clients. + *

    + * This is a varargs wrapper around {@link #getToolCallbacksFromSyncClients(List)} for + * easier usage when working with individual clients. + * @param mcpClients the synchronous MCP clients to get callbacks from + * @return a list of tool callbacks from all provided clients + * @see #getToolCallbacksFromSyncClients(List) + */ + public static List getToolCallbacksFromSyncClients(McpSyncClient... mcpClients) { + return getToolCallbacksFromSyncClients(List.of(mcpClients)); } - public static List getToolCallbacks(List mcpClients) { + /** + * Gets tool callbacks from a list of synchronous MCP clients. + *

    + * This method: + *

      + *
    1. Takes a list of synchronous MCP clients
    2. + *
    3. Creates a provider for each client
    4. + *
    5. Retrieves and combines all tool callbacks into a single list
    6. + *
    + * @param mcpClients the list of synchronous MCP clients to get callbacks from + * @return a list of tool callbacks from all provided clients + */ + public static List getToolCallbacksFromSyncClients(List mcpClients) { if (CollectionUtils.isEmpty(mcpClients)) { return List.of(); @@ -195,4 +217,40 @@ public static List getToolCallbacks(List mcpClients .toList(); } + /** + * Convenience method to get tool callbacks from multiple asynchronous MCP clients. + *

    + * This is a varargs wrapper around {@link #getToolCallbacksFromAsyncClinents(List)} + * for easier usage when working with individual clients. + * @param asynMcpClients the asynchronous MCP clients to get callbacks from + * @return a list of tool callbacks from all provided clients + * @see #getToolCallbacksFromAsyncClinents(List) + */ + public static List getToolCallbacksFromAsyncClients(McpAsyncClient... asynMcpClients) { + return getToolCallbacksFromAsyncClinents(List.of(asynMcpClients)); + } + + /** + * Gets tool callbacks from a list of asynchronous MCP clients. + *

    + * This method: + *

      + *
    1. Takes a list of asynchronous MCP clients
    2. + *
    3. Creates a provider for each client
    4. + *
    5. Retrieves and combines all tool callbacks into a single list
    6. + *
    + * @param asynMcpClients the list of asynchronous MCP clients to get callbacks from + * @return a list of tool callbacks from all provided clients + */ + public static List getToolCallbacksFromAsyncClinents(List asynMcpClients) { + + if (CollectionUtils.isEmpty(asynMcpClients)) { + return List.of(); + } + return asynMcpClients.stream() + .map(mcpClient -> List.of((new AsyncMcpToolCallbackProvider(mcpClient).getToolCallbacks()))) + .flatMap(List::stream) + .toList(); + } + } diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolCallback.java b/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java similarity index 95% rename from mcp/common/src/main/java/org/springframework/ai/mcp/McpToolCallback.java rename to mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java index b4e3ada139f..ffbe09303d6 100644 --- a/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolCallback.java +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java @@ -55,19 +55,18 @@ * @see McpSyncClient * @see Tool */ -public class McpToolCallback implements ToolCallback { +public class SyncMcpToolCallback implements ToolCallback { private final McpSyncClient mcpClient; private final Tool tool; /** - * Creates a new {@code McpToolCallback} instance. + * Creates a new {@code SyncMcpToolCallback} instance. * @param mcpClient the MCP client to use for tool execution * @param tool the MCP tool definition to adapt */ - - public McpToolCallback(McpSyncClient mcpClient, Tool tool) { + public SyncMcpToolCallback(McpSyncClient mcpClient, Tool tool) { this.mcpClient = mcpClient; this.tool = tool; } diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallbackProvider.java b/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallbackProvider.java index 3051ad7f008..ea18baf42e3 100644 --- a/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallbackProvider.java +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallbackProvider.java @@ -32,13 +32,13 @@ *
      *
    • Connects to an MCP server through a sync client
    • *
    • Lists and retrieves available tools from the server
    • - *
    • Creates {@link McpToolCallback} instances for each discovered tool
    • + *
    • Creates {@link SyncMcpToolCallback} instances for each discovered tool
    • *
    • Validates tool names to prevent duplicates
    • *
    *

    * Example usage:

    {@code
      * McpSyncClient mcpClient = // obtain MCP client
    - * ToolCallbackProvider provider = new McpToolCallbackProvider(mcpClient);
    + * ToolCallbackProvider provider = new SyncMcpToolCallbackProvider(mcpClient);
      *
      * // Get all available tools
      * ToolCallback[] tools = provider.getToolCallbacks();
    @@ -47,7 +47,7 @@
      * @author Christian Tzolov
      * @since 1.0.0
      * @see ToolCallbackProvider
    - * @see McpToolCallback
    + * @see SyncMcpToolCallback
      * @see McpSyncClient
      */
     
    @@ -56,7 +56,7 @@ public class SyncMcpToolCallbackProvider implements ToolCallbackProvider {
     	private final McpSyncClient mcpClient;
     
     	/**
    -	 * Creates a new {@code McpToolCallbackProvider} instance.
    +	 * Creates a new {@code SyncMcpToolCallbackProvider} instance.
     	 * @param mcpClient the MCP client to use for discovering tools
     	 */
     	public SyncMcpToolCallbackProvider(McpSyncClient mcpClient) {
    @@ -69,7 +69,7 @@ public SyncMcpToolCallbackProvider(McpSyncClient mcpClient) {
     	 * This method:
     	 * 
      *
    1. Retrieves the list of tools from the MCP server
    2. - *
    3. Creates a {@link McpToolCallback} for each tool
    4. + *
    5. Creates a {@link SyncMcpToolCallback} for each tool
    6. *
    7. Validates that there are no duplicate tool names
    8. *
    * @return an array of tool callbacks, one for each discovered tool @@ -81,7 +81,7 @@ public ToolCallback[] getToolCallbacks() { var toolCallbacks = this.mcpClient.listTools() .tools() .stream() - .map(tool -> new McpToolCallback(this.mcpClient, tool)) + .map(tool -> new SyncMcpToolCallback(this.mcpClient, tool)) .toArray(ToolCallback[]::new); validateToolCallbacks(toolCallbacks); @@ -106,6 +106,18 @@ private void validateToolCallbacks(ToolCallback[] toolCallbacks) { } } + /** + * Creates a list of tool callbacks from multiple MCP clients. + *

    + * This utility method: + *

      + *
    1. Takes a list of MCP clients
    2. + *
    3. Creates a provider for each client
    4. + *
    5. Retrieves and combines all tool callbacks into a single list
    6. + *
    + * @param mcpClients the list of MCP clients to create callbacks from + * @return a list of tool callbacks from all provided clients + */ public static List syncToolCallbacks(List mcpClients) { if (CollectionUtils.isEmpty(mcpClients)) { diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/McpHints.java b/mcp/common/src/main/java/org/springframework/ai/mcp/aot/McpHints.java similarity index 54% rename from mcp/common/src/main/java/org/springframework/ai/mcp/McpHints.java rename to mcp/common/src/main/java/org/springframework/ai/mcp/aot/McpHints.java index cc6b617b392..fcf8c81d2e5 100644 --- a/mcp/common/src/main/java/org/springframework/ai/mcp/McpHints.java +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/aot/McpHints.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.ai.mcp; +package org.springframework.ai.mcp.aot; import java.util.ArrayList; import java.util.Arrays; @@ -30,12 +30,36 @@ import org.springframework.lang.Nullable; /** + * Runtime hints registrar for Model Context Protocol (MCP) schema classes. + *

    + * This class provides GraalVM native image hints for MCP schema classes to ensure proper + * reflection access in native images. It: + *

      + *
    • Registers all nested classes of {@link McpSchema} for reflection
    • + *
    • Enables all member categories (fields, methods, etc.) for registered types
    • + *
    • Ensures proper serialization/deserialization in native images
    • + *
    + * * @author Josh Long * @since 1.0.0 + * @see RuntimeHintsRegistrar + * @see McpSchema */ @SuppressWarnings("unused") public class McpHints implements RuntimeHintsRegistrar { + /** + * Registers runtime hints for MCP schema classes. + *

    + * This method: + *

      + *
    1. Discovers all nested classes within {@link McpSchema}
    2. + *
    3. Registers each discovered class for reflection access
    4. + *
    5. Enables all member categories for complete reflection support
    6. + *
    + * @param hints the hints instance to register hints with + * @param classLoader the classloader to use (may be null) + */ @Override public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { var mcs = MemberCategory.values(); @@ -45,12 +69,32 @@ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) } } + /** + * Discovers all inner classes of a given class. + *

    + * This method recursively finds all nested classes (both declared and inherited) of + * the provided class and converts them to type references. + * @param clazz the class to find inner classes for + * @return a set of type references for all discovered inner classes + */ private Set innerClasses(Class clazz) { var indent = new HashSet(); this.findNestedClasses(clazz, indent); return indent.stream().map(TypeReference::of).collect(Collectors.toSet()); } + /** + * Recursively finds all nested classes of a given class. + *

    + * This method: + *

      + *
    1. Collects both declared and inherited nested classes
    2. + *
    3. Recursively processes each nested class
    4. + *
    5. Adds the class names to the provided set
    6. + *
    + * @param clazz the class to find nested classes for + * @param indent the set to collect class names in + */ private void findNestedClasses(Class clazz, Set indent) { var classes = new ArrayList>(); classes.addAll(Arrays.asList(clazz.getDeclaredClasses())); diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/customizer/McpAsyncClientCustomizer.java b/mcp/common/src/main/java/org/springframework/ai/mcp/customizer/McpAsyncClientCustomizer.java new file mode 100644 index 00000000000..b059bcbe3c5 --- /dev/null +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/customizer/McpAsyncClientCustomizer.java @@ -0,0 +1,44 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.mcp.customizer; + +import io.modelcontextprotocol.client.McpClient; + +/** + * Interface for customizing asynchronous MCP client configurations. + *

    + * This interface allows for customization of MCP client behavior through Spring's + * customizer pattern. Implementations can modify the client's configuration before it is + * used in the application. + *

    + * + * @author Christian Tzolov + * @since 1.0.0 + * @see io.modelcontextprotocol.client.McpClient.AsyncSpec + */ +public interface McpAsyncClientCustomizer { + + /** + * Customizes an asynchronous MCP client configuration. + *

    + * This method is called for each async MCP client being created, allowing for + * client-specific customizations based on the client's name and specification. + * @param name the name of the MCP client being customized + * @param spec the async specification to customize + */ + void customize(String name, McpClient.AsyncSpec spec); + +} diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/customizer/McpSyncClientCustomizer.java b/mcp/common/src/main/java/org/springframework/ai/mcp/customizer/McpSyncClientCustomizer.java new file mode 100644 index 00000000000..98c0a1cdca8 --- /dev/null +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/customizer/McpSyncClientCustomizer.java @@ -0,0 +1,44 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.mcp.customizer; + +import io.modelcontextprotocol.client.McpClient; + +/** + * Interface for customizing synchronous MCP client configurations. + *

    + * This interface allows for customization of MCP client behavior through Spring's + * customizer pattern. Implementations can modify the client's configuration before it is + * used in the application. + *

    + * + * @author Christian Tzolov + * @since 1.0.0 + * @see io.modelcontextprotocol.client.McpClient.SyncSpec + */ +public interface McpSyncClientCustomizer { + + /** + * Customizes a synchronous MCP client configuration. + *

    + * This method is called for each sync MCP client being created, allowing for + * client-specific customizations based on the client's name and specification. + * @param name the name of the MCP client being customized + * @param spec the sync specification to customize + */ + void customize(String name, McpClient.SyncSpec spec); + +} diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/package-info.java b/mcp/common/src/main/java/org/springframework/ai/mcp/package-info.java index fdd7a9d995b..df28217aff9 100644 --- a/mcp/common/src/main/java/org/springframework/ai/mcp/package-info.java +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/package-info.java @@ -14,6 +14,26 @@ * limitations under the License. */ +/** + * Core support for Model Context Protocol (MCP) integration in Spring AI. + *

    + * This package provides the foundational classes and utilities for integrating MCP with + * Spring AI's tool system. It includes: + *

      + *
    • Tool callback implementations for both synchronous and asynchronous MCP + * operations
    • + *
    • Tool callback providers that discover and expose MCP tools
    • + *
    • Utility classes for converting between Spring AI and MCP tool representations
    • + *
    • Support for customizing MCP client behavior
    • + *
    + *

    + * The classes in this package enable seamless integration between Spring AI applications + * and MCP servers, allowing language models to discover and invoke tools through a + * standardized protocol. + * + * @author Christian Tzolov + * @since 1.0.0 + */ @NonNullApi @NonNullFields package org.springframework.ai.mcp; diff --git a/mcp/common/src/main/resources/META-INF/spring/aot.factories b/mcp/common/src/main/resources/META-INF/spring/aot.factories index dbf904981fb..b961ce5bcfb 100644 --- a/mcp/common/src/main/resources/META-INF/spring/aot.factories +++ b/mcp/common/src/main/resources/META-INF/spring/aot.factories @@ -1,2 +1,2 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\ - org.springframework.ai.mcp.McpHints \ No newline at end of file + org.springframework.ai.mcp.aot.McpHints \ No newline at end of file diff --git a/mcp/common/src/test/java/org/springframework/ai/mcp/McpToolCallbackProviderTests.java b/mcp/common/src/test/java/org/springframework/ai/mcp/SyncMcpToolCallbackProviderTests.java similarity index 98% rename from mcp/common/src/test/java/org/springframework/ai/mcp/McpToolCallbackProviderTests.java rename to mcp/common/src/test/java/org/springframework/ai/mcp/SyncMcpToolCallbackProviderTests.java index 0fc86888df7..608ae9f2017 100644 --- a/mcp/common/src/test/java/org/springframework/ai/mcp/McpToolCallbackProviderTests.java +++ b/mcp/common/src/test/java/org/springframework/ai/mcp/SyncMcpToolCallbackProviderTests.java @@ -33,7 +33,7 @@ import io.modelcontextprotocol.spec.McpSchema.Tool; @ExtendWith(MockitoExtension.class) -class McpToolCallbackProviderTests { +class SyncMcpToolCallbackProviderTests { @Mock private McpSyncClient mcpClient; diff --git a/mcp/common/src/test/java/org/springframework/ai/mcp/McpToolCallbackTests.java b/mcp/common/src/test/java/org/springframework/ai/mcp/SyncMcpToolCallbackTests.java similarity index 92% rename from mcp/common/src/test/java/org/springframework/ai/mcp/McpToolCallbackTests.java rename to mcp/common/src/test/java/org/springframework/ai/mcp/SyncMcpToolCallbackTests.java index d0eefaf75e5..7131385258b 100644 --- a/mcp/common/src/test/java/org/springframework/ai/mcp/McpToolCallbackTests.java +++ b/mcp/common/src/test/java/org/springframework/ai/mcp/SyncMcpToolCallbackTests.java @@ -31,7 +31,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class McpToolCallbackTests { +class SyncMcpToolCallbackTests { @Mock private McpSyncClient mcpClient; @@ -45,7 +45,7 @@ void getToolDefinitionShouldReturnCorrectDefinition() { when(tool.name()).thenReturn("testTool"); when(tool.description()).thenReturn("Test tool description"); - McpToolCallback callback = new McpToolCallback(mcpClient, tool); + SyncMcpToolCallback callback = new SyncMcpToolCallback(mcpClient, tool); // Act var toolDefinition = callback.getToolDefinition(); @@ -62,7 +62,7 @@ void callShouldHandleJsonInputAndOutput() { CallToolResult callResult = mock(CallToolResult.class); when(mcpClient.callTool(any(CallToolRequest.class))).thenReturn(callResult); - McpToolCallback callback = new McpToolCallback(mcpClient, tool); + SyncMcpToolCallback callback = new SyncMcpToolCallback(mcpClient, tool); // Act String response = callback.call("{\"param\":\"value\"}"); diff --git a/pom.xml b/pom.xml index 2136b768b93..f47ff2f5786 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,12 @@ spring-ai-bom spring-ai-core spring-ai-test + spring-ai-spring-boot-autoconfigure + + auto-configurations/spring-ai-mcp-client + auto-configurations/spring-ai-mcp-server + spring-ai-retry spring-ai-spring-boot-docker-compose spring-ai-spring-boot-testcontainers @@ -127,7 +132,11 @@ spring-ai-spring-boot-starters/spring-ai-starter-zhipuai spring-ai-spring-boot-starters/spring-ai-starter-moonshot - spring-ai-spring-boot-starters/spring-ai-starter-mcp + spring-ai-spring-boot-starters/spring-ai-starter-mcp-client + spring-ai-spring-boot-starters/spring-ai-starter-mcp-server + spring-ai-spring-boot-starters/spring-ai-starter-mcp-client-webflux + spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webflux + spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webmvc spring-ai-integration-tests @@ -890,6 +899,16 @@ lead + + tzolov + Christian Tzolov + christian tzolov at broadcom.com + Broadcom + http://www.spring.io + + lead + + diff --git a/spring-ai-bom/pom.xml b/spring-ai-bom/pom.xml index 5979f150857..093caee4dc5 100644 --- a/spring-ai-bom/pom.xml +++ b/spring-ai-bom/pom.xml @@ -310,6 +310,18 @@ ${project.version} + + org.springframework.ai + spring-ai-mcp-client-spring-boot-autoconfigure + ${project.version} + + + + org.springframework.ai + spring-ai-mcp-server-spring-boot-autoconfigure + ${project.version} + + org.springframework.ai @@ -589,10 +601,35 @@ org.springframework.ai - spring-ai-mcp-spring-boot-starter + spring-ai-mcp-client-spring-boot-starter + ${project.version} + + + + org.springframework.ai + spring-ai-mcp-server-spring-boot-starter ${project.version} + + org.springframework.ai + spring-ai-mcp-client-webflux-spring-boot-starter + ${project.version} + + + + org.springframework.ai + spring-ai-mcp-server-webflux-spring-boot-starter + ${project.version} + + + + org.springframework.ai + spring-ai-mcp-server-webmvc-spring-boot-starter + ${project.version} + + + diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/mcp/java-mcp-client-architecture.jpg b/spring-ai-docs/src/main/antora/modules/ROOT/images/mcp/java-mcp-client-architecture.jpg new file mode 100644 index 0000000000000000000000000000000000000000..688a2b4ad0f92090bf0033ab5eb3b20fce1a8484 GIT binary patch literal 676694 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

    * - * {@code moonshot-v1-auto} can select the appropriate model based on the number of Tokens occupied by the current context. The available models for selection include: + * {@code moonshot-v1-auto} can select the appropriate model based on the number of + * Tokens occupied by the current context. The available models for selection include: *
      *
    • {@code moonshot-v1-8k}
    • *
    • {@code moonshot-v1-32k}
    • *
    • {@code moonshot-v1-128k}
    • *
    - *

    {@code moonshot-v1-auto} can be regarded as a model router, which decides which specific model to select based on the number of Tokens occupied by the current context. In terms of performance and output, {@code moonshot-v1-auto} is indistinguishable from the aforementioned models.

    - * The routing rules for the model selected by {@code moonshot-v1-auto} are as follows: + *

    + * {@code moonshot-v1-auto} can be regarded as a model router, which decides which + * specific model to select based on the number of Tokens occupied by the current + * context. In terms of performance and output, {@code moonshot-v1-auto} is + * indistinguishable from the aforementioned models. + *

    + * The routing rules for the model selected by {@code moonshot-v1-auto} are as + * follows: *
      *
    • If {@code total_tokens ≤ 8 * 1024}, choose {@code moonshot-v1-8k}.
    • - *
    • If {@code 8 * 1024 < total_tokens ≤ 32 * 1024}, choose {@code moonshot-v1-32k}.
    • + *
    • If {@code 8 * 1024 < total_tokens ≤ 32 * 1024}, choose + * {@code moonshot-v1-32k}.
    • *
    • If {@code total_tokens > 32 * 1024}, choose {@code moonshot-v1-128k}.
    • *
    - * The calculation formula is: - * {@code total_tokens = prompt_tokens + max_tokens} - *

    The total number of Tokens is composed of two parts: + * The calculation formula is: {@code total_tokens = prompt_tokens + max_tokens} + *

    + * The total number of Tokens is composed of two parts: *

      - *
    • {@code prompt_tokens}: The number of Tokens occupied by the input prompt (Prompt).
    • - *
    • {@code max_tokens}: The maximum number of Tokens expected to be generated as output.
    • + *
    • {@code prompt_tokens}: The number of Tokens occupied by the input prompt + * (Prompt).
    • + *
    • {@code max_tokens}: The maximum number of Tokens expected to be generated as + * output.
    • *
    */ public enum ChatModel implements ChatModelDescription { From 10460152a53048d52d8b1d5b7d10fa5d21f5af42 Mon Sep 17 00:00:00 2001 From: Xiaojie Wang Date: Sun, 9 Feb 2025 18:44:15 +0800 Subject: [PATCH 50/51] Add available models for Moonshot Chat Completion Models. Signed-off-by: Xiaojie Wang --- .../ai/moonshot/api/MoonshotApi.java | 24 +++++++++++++++++++ .../ROOT/pages/api/chat/moonshot-chat.adoc | 6 ++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java b/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java index 532fb851b8b..3d63406841f 100644 --- a/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java +++ b/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java @@ -48,6 +48,7 @@ * * @author Geng Rong * @author Thomas Vitale + * @author Wang Xiaojie */ public class MoonshotApi { @@ -207,14 +208,37 @@ public enum ChatCompletionFinishReason { * Moonshot Chat Completion Models: * *
      + *
    • MOONSHOT_V1_AUTO - moonshot-v1-auto
    • *
    • MOONSHOT_V1_8K - moonshot-v1-8k
    • *
    • MOONSHOT_V1_32K - moonshot-v1-32k
    • *
    • MOONSHOT_V1_128K - moonshot-v1-128k
    • *
    + * + * {@code moonshot-v1-auto} can select the appropriate model based on the number of Tokens occupied by the current context. The available models for selection include: + *
      + *
    • {@code moonshot-v1-8k}
    • + *
    • {@code moonshot-v1-32k}
    • + *
    • {@code moonshot-v1-128k}
    • + *
    + *

    {@code moonshot-v1-auto} can be regarded as a model router, which decides which specific model to select based on the number of Tokens occupied by the current context. In terms of performance and output, {@code moonshot-v1-auto} is indistinguishable from the aforementioned models.

    + * The routing rules for the model selected by {@code moonshot-v1-auto} are as follows: + *
      + *
    • If {@code total_tokens ≤ 8 * 1024}, choose {@code moonshot-v1-8k}.
    • + *
    • If {@code 8 * 1024 < total_tokens ≤ 32 * 1024}, choose {@code moonshot-v1-32k}.
    • + *
    • If {@code total_tokens > 32 * 1024}, choose {@code moonshot-v1-128k}.
    • + *
    + * The calculation formula is: + * {@code total_tokens = prompt_tokens + max_tokens} + *

    The total number of Tokens is composed of two parts: + *

      + *
    • {@code prompt_tokens}: The number of Tokens occupied by the input prompt (Prompt).
    • + *
    • {@code max_tokens}: The maximum number of Tokens expected to be generated as output.
    • + *
    */ public enum ChatModel implements ChatModelDescription { // @formatter:off + MOONSHOT_V1_AUTO("moonshot-v1-auto"), MOONSHOT_V1_8K("moonshot-v1-8k"), MOONSHOT_V1_32K("moonshot-v1-32k"), MOONSHOT_V1_128K("moonshot-v1-128k"); diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/moonshot-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/moonshot-chat.adoc index eafbd7c129d..62a4eb7d9bb 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/moonshot-chat.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/moonshot-chat.adoc @@ -89,7 +89,7 @@ The prefix `spring.ai.moonshot.chat` is the property prefix that lets you config | spring.ai.moonshot.chat.enabled | Enable Moonshot chat model. | true | spring.ai.moonshot.chat.base-url | Optional overrides the spring.ai.moonshot.base-url to provide chat specific url | - | spring.ai.moonshot.chat.api-key | Optional overrides the spring.ai.moonshot.api-key to provide chat specific api-key | - -| spring.ai.moonshot.chat.options.model | This is the Moonshot Chat model to use | `moonshot-v1-8k` (the `moonshot-v1-8k`, `moonshot-v1-32k`, and `moonshot-v1-128k` point to the latest model versions) +| spring.ai.moonshot.chat.options.model | This is the Moonshot Chat model to use | `moonshot-v1-8k` (the `moonshot-v1-auto`, `moonshot-v1-8k`, `moonshot-v1-32k`, and `moonshot-v1-128k` point to the latest model versions) | spring.ai.moonshot.chat.options.maxTokens | The maximum number of tokens to generate in the chat completion. The total length of input tokens and generated tokens is limited by the model's context length. | - | spring.ai.moonshot.chat.options.temperature | The sampling temperature to use that controls the apparent creativity of generated completions. Higher values will make output more random while lower values will make results more focused and deterministic. It is not recommended to modify temperature and top_p for the same completions request as the interaction of these two settings is difficult to predict. | 0.7 | spring.ai.moonshot.chat.options.topP | An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both. | 1.0 @@ -103,6 +103,10 @@ NOTE: You can override the common `spring.ai.moonshot.base-url` and `spring.ai.m The `spring.ai.moonshot.chat.base-url` and `spring.ai.moonshot.chat.api-key` properties if set take precedence over the common properties. This is useful if you want to use different Moonshot accounts for different models and different model endpoints. +NOTE: When the value of `spring.ai.moonshot.chat.options.model` is set to `moonshot-v1-auto`, it can select the appropriate model based on the number of Tokens occupied by the current context. +The available models for selection include: `moonshot-v1-8k`, `moonshot-v1-32k` and `moonshot-v1-128k`. +`moonshot-v1-auto` can be considered as a model router. It decides which specific model to select based on the number of Tokens occupied by the current context. In terms of performance and output, `moonshot-v1-auto` is indistinguishable from the aforementioned models. + TIP: All properties prefixed with `spring.ai.moonshot.chat.options` can be overridden at runtime by adding a request specific <> to the `Prompt` call. == Runtime Options [[chat-options]] From f54123bc8eca34b1134f9823f07bc828e66d00f4 Mon Sep 17 00:00:00 2001 From: Xiaojie Wang Date: Sun, 9 Feb 2025 19:32:32 +0800 Subject: [PATCH 51/51] Add available models for Moonshot Chat Completion Models. Signed-off-by: Xiaojie Wang --- .../ai/moonshot/api/MoonshotApi.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java b/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java index 3d63406841f..3b4b9f66f5c 100644 --- a/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java +++ b/models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/api/MoonshotApi.java @@ -214,25 +214,35 @@ public enum ChatCompletionFinishReason { *
  • MOONSHOT_V1_128K - moonshot-v1-128k
  • * * - * {@code moonshot-v1-auto} can select the appropriate model based on the number of Tokens occupied by the current context. The available models for selection include: + * {@code moonshot-v1-auto} can select the appropriate model based on the number of + * Tokens occupied by the current context. The available models for selection include: *
      *
    • {@code moonshot-v1-8k}
    • *
    • {@code moonshot-v1-32k}
    • *
    • {@code moonshot-v1-128k}
    • *
    - *

    {@code moonshot-v1-auto} can be regarded as a model router, which decides which specific model to select based on the number of Tokens occupied by the current context. In terms of performance and output, {@code moonshot-v1-auto} is indistinguishable from the aforementioned models.

    - * The routing rules for the model selected by {@code moonshot-v1-auto} are as follows: + *

    + * {@code moonshot-v1-auto} can be regarded as a model router, which decides which + * specific model to select based on the number of Tokens occupied by the current + * context. In terms of performance and output, {@code moonshot-v1-auto} is + * indistinguishable from the aforementioned models. + *

    + * The routing rules for the model selected by {@code moonshot-v1-auto} are as + * follows: *
      *
    • If {@code total_tokens ≤ 8 * 1024}, choose {@code moonshot-v1-8k}.
    • - *
    • If {@code 8 * 1024 < total_tokens ≤ 32 * 1024}, choose {@code moonshot-v1-32k}.
    • + *
    • If {@code 8 * 1024 < total_tokens ≤ 32 * 1024}, choose + * {@code moonshot-v1-32k}.
    • *
    • If {@code total_tokens > 32 * 1024}, choose {@code moonshot-v1-128k}.
    • *
    - * The calculation formula is: - * {@code total_tokens = prompt_tokens + max_tokens} - *

    The total number of Tokens is composed of two parts: + * The calculation formula is: {@code total_tokens = prompt_tokens + max_tokens} + *

    + * The total number of Tokens is composed of two parts: *

      - *
    • {@code prompt_tokens}: The number of Tokens occupied by the input prompt (Prompt).
    • - *
    • {@code max_tokens}: The maximum number of Tokens expected to be generated as output.
    • + *
    • {@code prompt_tokens}: The number of Tokens occupied by the input prompt + * (Prompt).
    • + *
    • {@code max_tokens}: The maximum number of Tokens expected to be generated as + * output.
    • *
    */ public enum ChatModel implements ChatModelDescription {