Bug description
When implementing an MCP server using Spring AI 1.1.0-M1 and annotating beans with @McpTool, the tools are not registered at server startup, even though the property spring.ai.mcp.server.annotation-scanner.enabled is explicitly set to true. The documentation (https://docs.spring.io/spring-ai/reference/1.1/api/mcp/mcp-server-boot-starter-docs.html) suggests that this should work without manual registration.
As a workaround, tools are registered if I annotate beans with @Tool and create a bean of type ToolCallbackProvider that manually passes the beans (e.g., via MethodToolCallbackProvider.builder().toolObjects(productService, timeService).build()). This workaround was standard in 1.0.x, but the documentation for 1.1.x indicates that @McpTool with annotation scanning should suffice.
This points to a problem either in the property spring.ai.mcp.server.annotation-scanner.enabled or the annotation scanning functionality for MCP tools in 1.1.x.
Environment
- Spring AI version: 1.1.0-M1
- Java version: 21
- MCP server transport: HTTP Streamable
- Property set: spring.ai.mcp.server.annotation-scanner.enabled=true
- Beans annotated with @McpTool
Steps to reproduce
1. Create a Spring Boot MCP server with Spring AI 1.1.0-M1.
2. Annotate beans with @McpTool and define tool methods.
3. Set spring.ai.mcp.server.annotation-scanner.enabled=true.
4. Start the server.
5. Observe that tools are not registered.
6. Switch to @Tool annotation and manually register beans via a ToolCallbackProvider bean; tools are registered as expected.
Expected behavior
Beans annotated with @McpTool should be automatically registered as tools when the annotation scanner is enabled, per documentation, without requiring manual registration.
Minimal Complete Reproducible example
@Bean
ToolCallbackProvider productTools(ProductService productService, TimeService timeService) {
  return MethodToolCallbackProvider.builder()
      .toolObjects(productService, timeService)
      .build();
}
But this should not be necessary when using @McpTool and the annotation scanner property.
Please advise if there is a known issue or if a fix is planned for future releases.
Comment From: ilayaperumalg
@oburgosm Could you try the following and see if it works:
- Use @McpToolannotation for your tool
- Register your ToolCallbackProvider to the client via .toolcallbacks(ToolCallbackProvider)
Comment From: oburgosm
Thank you for your response. I want to clarify that I do not have a client implementation using Spring AI. To test the MCP server, I am using MCPInspector.
The behavior I observe:
- When I annotate beans with @McpTool, MCPInspector does not list any tools when exploring the server.
- However, if I use @Tool and register the beans manually via a ToolCallbackProvider, MCPInspector does list the tools as expected.
So, this issue not only affects the server's internal registration, but also the visibility of tools from external clients like MCPInspector.
Is there any additional configuration I should apply so MCPInspector can detect tools annotated with @McpTool? Or does this confirm the problem is with the automatic registration of tools when using the new annotation?
Let me know if you need a code snippet or example of how I am using MCPInspector.
Comment From: tzolov
@oburgosm can you share please which mcp server boot starter are you using?
Comment From: oburgosm
This issue occurs with both spring-ai-starter-mcp-server-webmvc and spring-ai-starter-mcp-server-webflux.
Comment From: tzolov
Thanks @oburgosm and what is the inspector URL you've been using?
Comment From: tzolov
I've successfully tested the https://github.com/tzolov/spring-ai-mcp-blogpost/tree/main/mcp-weather-server with the MCP Inspector yesterday. Can spot what is the difference with your server?
Comment From: tzolov
@oburgosm can you share the signature of the tool method. Could it be that the return type uses Generics?
Comment From: oburgosm
Thanks for your follow-up.
- MCPInspector connects to http://localhost:8081/my-app/mcp. I have also tested with port8080and context-path/, with the same result. MCPInspector connects successfully in all cases, so I don't think the port or context-path is the issue.
- Some methods annotated with @McpTooluse generics.
- I tested with the referenced sample project tzolov/spring-ai-mcp-blogpost, specifically the mcp-weather-servermodule. With the default configuration, the tool list is shown. However, settingspring.ai.mcp.server.typetoASYNCresults in an empty tool list, with both webmvc and webflux starters.
- Additionally, if I change the type from ASYNC to SYNC in my application, MCPInspector returns errors like the following:
{
  "error": "[\n  {\n    \"received\": \"array\",\n    \"code\": \"invalid_literal\",\n    \"expected\": \"object\",\n    \"path\": [\n      \"tools\",\n      7,\n      \"outputSchema\",\n      \"type\"\n    ],\n    \"message\": \"Invalid literal value, expected \\\"object\\\"\"\n  },\n  {\n    \"received\": \"array\",\n    \"code\": \"invalid_literal\",\n    \"expected\": \"object\",\n    \"path\": [\n      \"tools\",\n      8,\n      \"outputSchema\",\n      \"type\"\n    ],\n    \"message\": \"Invalid literal value, expected \\\"object\\\"\"\n  },\n  {\n    \"received\": \"array\",\n    \"code\": \"invalid_literal\",\n    \"expected\": \"object\",\n    \"path\": [\n      \"tools\",\n      9,\n      \"outputSchema\",\n      \"type\"\n    ],\n    \"message\": \"Invalid literal value, expected \\\"object\\\"\"\n  },\n  {\n    \"received\": \"array\",\n    \"code\": \"invalid_literal\",\n    \"expected\": \"object\",\n    \"path\": [\n      \"tools\",\n      10,\n      \"outputSchema\",\n      \"type\"\n    ],\n    \"message\": \"Invalid literal value, expected \\\"object\\\"\"\n  },\n  {\n    \"received\": \"array\",\n    \"code\": \"invalid_literal\",\n    \"expected\": \"object\",\n    \"path\": [\n      \"tools\",\n      11,\n      \"outputSchema\",\n      \"type\"\n    ],\n    \"message\": \"Invalid literal value, expected \\\"object\\\"\"\n  },\n  {\n    \"received\": \"array\",\n    \"code\": \"invalid_literal\",\n    \"expected\": \"object\",\n    \"path\": [\n      \"tools\",\n      12,\n      \"outputSchema\",\n      \"type\"\n    ],\n    \"message\": \"Invalid literal value, expected \\\"object\\\"\"\n  },\n  {\n    \"received\": \"array\",\n    \"code\": \"invalid_literal\",\n    \"expected\": \"object\",\n    \"path\": [\n      \"tools\",\n      13,\n      \"outputSchema\",\n      \"type\"\n    ],\n    \"message\": \"Invalid literal value, expected \\\"object\\\"\"\n  }\n]"
}
Here is the relevant part of my service implementation (where the methods are annotated with @McpTool):
@Service
public class ProductServiceImpl implements ProductService {
  @Autowired
  private ProductRepository productRepository;
  @Autowired
  private ClothingRepository clothingRepository;
  @Autowired
  private ClothingVariationRepository clothingVariationRepository;
  @Autowired
  private PerfumeRepository perfumeRepository;
  @Autowired
  private PerfumeVariationRepository perfumeVariationRepository;
  @Autowired
  private ShoesRepository shoesRepository;
  @Autowired
  private ShoesVariationRepository shoesVariationRepository;
  // Product operations
  @Override
  @McpTool(description = "Retrieve a product by its ID")
  public Optional<Product> getProductById(Long id) { ... }
  @Override
  @McpTool(description = "Retrieve all products")
  public List<Product> getAllProducts() { ... }
  @Override
  @McpTool(description = "Delete a product by its ID")
  public void deleteProduct(Long id) { ... }
  // Clothing operations
  @Override
  @McpTool(description = "Save a new clothing item")
  public Clothing saveClothing(Clothing clothing) { ... }
  @Override
  @McpTool(description = "Retrieve a clothing item by its ID")
  public Optional<Clothing> getClothingById(Long id) { ... }
  @Override
  @McpTool(description = "Retrieve all clothing items")
  public List<Clothing> getAllClothing() { ... }
  @Override
  @McpTool(description = "Delete a clothing item by its ID")
  public void deleteClothing(Long id) { ... }
  // ClothingVariation operations
  @Override
  @McpTool(description = "Save a new clothing variation")
  public ClothingVariation saveClothingVariation(ClothingVariation variation) { ... }
  @Override
  @McpTool(description = "Retrieve a clothing variation by its ID")
  public Optional<ClothingVariation> getClothingVariationById(Long id) { ... }
  @Override
  @McpTool(description = "Retrieve all clothing variations")
  public List<ClothingVariation> getAllClothingVariations() { ... }
  @Override
  @McpTool(description = "Retrieve clothing variations by size")
  public Optional<ClothingVariation> getClothingVariationsBySize(String size) { ... }
  @Override
  @McpTool(description = "Delete a clothing variation by its ID")
  public void deleteClothingVariation(Long id) { ... }
  // Perfume operations
  @Override
  @McpTool(description = "Save a new perfume")
  public Perfume savePerfume(Perfume perfume) { ... }
  @Override
  @McpTool(description = "Retrieve a perfume by its ID")
  public Optional<Perfume> getPerfumeById(Long id) { ... }
  @Override
  @McpTool(description = "Retrieve a perfume by its name")
  public Optional<Perfume> getPerfumeByName(String name) { ... }
  @Override
  @McpTool(description = "Retrieve all perfumes")
  public List<Perfume> getAllPerfumes() { ... }
  @Override
  @McpTool(description = "Delete a perfume by its ID")
  public void deletePerfume(Long id) { ... }
  // PerfumeVariation operations
  @Override
  @McpTool(description = "Save a new perfume variation")
  public PerfumeVariation savePerfumeVariation(PerfumeVariation variation) { ... }
  @Override
  @McpTool(description = "Retrieve a perfume variation by its ID")
  public Optional<PerfumeVariation> getPerfumeVariationById(Long id) { ... }
  @Override
  @McpTool(description = "Retrieve all perfume variations")
  public List<PerfumeVariation> getAllPerfumeVariations() { ... }
  @Override
  @McpTool(description = "Retrieve perfume variations by volume")
  public Optional<PerfumeVariation> getPerfumeVariationsByVolume(int volume) { ... }
  @Override
  @McpTool(description = "Delete a perfume variation by its ID")
  public void deletePerfumeVariation(Long id) { ... }
  // Shoes operations
  @Override
  @McpTool(description = "Save a new shoes item")
  public Shoes saveShoes(Shoes shoes) { ... }
  @Override
  @McpTool(description = "Retrieve a shoes item by its ID")
  public Optional<Shoes> getShoesById(Long id) { ... }
  @Override
  @McpTool(description = "Retrieve all shoes items")
  public List<Shoes> getAllShoes() { ... }
  @Override
  @McpTool(description = "Delete a shoes item by its ID")
  public void deleteShoes(Long id) { ... }
  // ShoesVariation operations
  @Override
  @McpTool(description = "Save a new shoes variation")
  public ShoesVariation saveShoesVariation(ShoesVariation variation) { ... }
  @Override
  @McpTool(description = "Retrieve a shoes variation by its ID")
  public Optional<ShoesVariation> getShoesVariationById(Long id) { ... }
  @Override
  @McpTool(description = "Retrieve all shoes variations")
  public List<ShoesVariation> getAllShoesVariations() { ... }
  @Override
  @McpTool(description = "Retrieve shoes variations by size")
  public Optional<ShoesVariation> getShoesVariationsBySize(int size) { ... }
  @Override
  @McpTool(description = "Delete a shoes variation by its ID")
  public void deleteShoesVariation(Long id) { ... }
}
Let me know if you need more details.
Comment From: oburgosm
After further debugging, I can confirm the following:
- With spring.ai.mcp.server.type=SYNC:
- Methods returning void, a Java object, orOptional<Object>are listed correctly by MCPInspector.
- 
Methods returning a List<Object>cause errors when listing tools. For example, for a method like:java @McpTool(description = "Retrieve all clothing items") public List<Clothing> getAllClothing() {}MCPInspector returns:{ "error": "[{\"received\": \"array\", \"code\": \"invalid_literal\", \"expected\": \"object\", \"path\": [\"tools\",0,\"outputSchema\",\"type\"], \"message\": \"Invalid literal value, expected \\\"object\\\"\"}]" }
- 
With spring.ai.mcp.server.type=ASYNC:
- The list of tools is always empty.
So, I believe there are two bugs: 1. No tools are listed when the type is ASYNC. 2. Listing tools fails for methods returning a list.
Let me know if you need more details or examples.
Comment From: YunKuiLu
Hi @tzolov @ilayaperumalg @oburgosm , I also encountered this problem, and I created a pr to fix it. Can you help review it? #4396 The modified code runs correctly on my end.
~~It should be because the loading order of AutoConfiguration is incorrect, resulting in @McpTool being unable to register into Spring properly.~~
Comment From: quaff
2. Listing tools fails for methods returning a list.
It's duplicate of GH-4373 which should be fixed by https://github.com/modelcontextprotocol/java-sdk/commit/d8959ef854aa75748bfa6d383c256bc9db2a8836
Comment From: ilayaperumalg
@oburgosm Could you try the latest Spring AI 1.1.0-SNAPSHOT which has the changes from https://github.com/modelcontextprotocol/java-sdk/commit/d8959ef854aa75748bfa6d383c256bc9db2a8836? Also, if you are using the MCP annotations, please try the latest 0.4.0-SNAPSHOT with the latest changes on type conversion updates?
Comment From: oburgosm
Thank you for the update and for addressing the issue.
I have tested with version 1.1.0-SNAPSHOT, as suggested. I can confirm that the problem related to issue #4373 is now resolved, both with the webmvc and webflux starters, as also mentioned by @quaff.
However, the other problem related to the ASYNC server type still persists. As far as I can see, the solution proposed by @YunKuiLu has not yet been merged. Once that or another fix is available, I will be happy to test again.
Thanks for your support!
Comment From: ilayaperumalg
@oburgosm Thank you for the quick follow-up and the confirmation!
Comment From: YunKuiLu
@oburgosm Sorry, I mistakenly thought you were using the same Stateless Mcp Server as me. Your issue is actually different from mine.
However, the other problem related to the ASYNC server type still persists.
Your issue is caused by:
When you set spring.ai.mcp.server.type to async, your tool's return type should be one of Mono, Flux, or Publisher.
Comment From: oburgosm
@YunKuiLu I’ve verified that if I change my method signatures to return Mono, Flux, or Publisher, the tools are listed correctly with ASYNC mode.
Is this the intended behavior? If so, it should be clearly documented. From my perspective, it’s a limitation that changing the server type to ASYNC also requires updating all the method signatures of exposed tools. This makes switching modes less straightforward for existing projects.
Clear documentation and rationale for this requirement would be helpful for users.
Comment From: tzolov
Is this the intended behavior? If so, it should be clearly documented. From my perspective, it’s a limitation that changing the server type to ASYNC also requires updating all the method signatures of exposed tools. This makes switching modes less straightforward for existing projects.
This is intentional indeed. The thinking behind is that if you are using an reactive MCP Server (e.g. ASYNC) then all its specifications (prompt, tools, resources ) should be implemented with the reactive paradigm and vs. verse. It is error prone to mix blocking (imperative) code inside a reactive, ,unless you know what you are doing.
But I agree that we have to clarify this in the reference docs!
Also we merged the #4396 (thank you @YunKuiLu ) that fixes the handling for stateless streamable http servers as well. Please give it a try.
Comment From: oburgosm
Thanks for the clarification and detailed reasoning @tzolov .
If I understand correctly, having spring.ai.mcp.server.type=ASYNC only makes sense when the method signatures use Mono, Flux, or Publisher, and mixing imperative and reactive styles is not supported or recommended. In that case, I wonder what is the practical purpose of the spring.ai.mcp.server.type property.
From a user perspective, it might be more intuitive if the starter determined the mode: spring-ai-mcp-webmvc-starter would always use SYNC, and spring-ai-mcp-webflux-starter would always use ASYNC. Otherwise, having a property to switch modes could be confusing, especially since switching the property also requires refactoring all exposed tool method signatures to match the selected mode.
Is there a use case or technical reason why the property is exposed instead of the starter enforcing the mode? If not, I think it would make the developer experience simpler and safer if the mode was determined by the chosen starter, or at least this requirement was clearly documented.
Let me know if I'm missing something. Thanks!
Comment From: tzolov
@oburgosm , currently all server transports spring-ai-starter-mcp-server , spring-ai-starter-mcp-server-webmvc, spring-ai-starter-mcp-server-webflux (check this) can be used in SYNC or ASYNC mode: https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-server-boot-starter-docs.html#_syncasync_server_api_options
If we decide to replace the spring.ai.mcp.server.type by dedicated boot-starters we have to add 3 additional boot starters e.g. spring-ai-starter-mcp-server-async , spring-ai-starter-mcp-server-webmvc-async, spring-ai-starter-mcp-server-webflux-async.  Would this be better. 
Also this issue made me think if we should allow mixing imperative and reactive styles in spring-ai. For example if you have SYNC configuration and find reactive @McpTool method definition, we can try to wrap latter into a non-reactive handler and other way around if ASYNC configuration discovers a sync handler it can try to convert it into an a reactive one (this comes with a cost) ..... But I'm not sure if this would be an improvement or bring more confusion.
Comment From: markpollack
Changed the label to 1.1 M3 as there is still ongoing discussion. Perhaps we should close this an open new ones as the scope of the conversation has changed and there have already been some PRs merged as a result of this issue.
Comment From: sivaprasadreddy
I am also facing this issue while using @McpTool support in Spring AI 1.1.0-SNAPSHOT.
I have created a simple MCP Server with STDIO protocol. Its working fine with @Tool and registering a List<ToolCallback> bean. But when I use @McpTool and test it from MCPInspector, it is throwing the following error:
STDIO transport: command=/Users/siva/.local/share/mise/installs/java/temurin-24.0.1+9/bin/java, args=-jar,/Users/siva/Siva/Projects/My/sivalabs-mcp/sivalabs-mcp-server-stdio/target/sivalabs-mcp-server-stdio-0.0.1.jar
Created client transport
Created server transport
Received POST message for sessionId 080fda54-4f11-4279-8d52-ae23fc5e4937
Error from MCP server: SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)
    at JSON.parse (<anonymous>)
    at deserializeMessage (file:///Users/siva/.npm/_npx/9eac9498388ae25e/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js:26:44)
    at ReadBuffer.readMessage (file:///Users/siva/.npm/_npx/9eac9498388ae25e/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js:19:16)
    at StdioClientTransport.processReadBuffer (file:///Users/siva/.npm/_npx/9eac9498388ae25e/node_modules/@modelcontextprotocol/sdk/dist/esm/client/stdio.js:141:50)
    at Socket.<anonymous> (file:///Users/siva/.npm/_npx/9eac9498388ae25e/node_modules/@modelcontextprotocol/sdk/dist/esm/client/stdio.js:103:22)
    at Socket.emit (node:events:518:28)
    at addChunk (node:internal/streams/readable:561:12)
    at readableAddChunkPushByteMode (node:internal/streams/readable:512:3)
    at Readable.push (node:internal/streams/readable:392:5)
    at Pipe.onStreamRead (node:internal/stream_base_commons:189:23)
I tried the weather sample from spring-ai-examples and it is also throwing the same error.
Here is a reproducer example: https://github.com/sivaprasadreddy/sivalabs-mcp/tree/ai-1.1.0-SNAPSHOT
- Checkout the branch ai-1.1.0-SNAPSHOT
- cd sivalabs-mcp-server-stdio
- mvn clean package
- npx @modelcontextprotocol/inspector@latest
Transport Type: STDIO
Command: java
Arguments: -jar /sivalabs-mcp/sivalabs-mcp-server-stdio/target/sivalabs-mcp-server-stdio-0.0.1.jar
NOTE: @McpTool support working fine with webmvc support (sse, streamable, stateless).
Comment From: ilayaperumalg
@sivaprasadreddy Thanks for reporting the issue. It would be better to have a separate issue to track it. I went ahead and created one here: https://github.com/spring-projects/spring-ai/issues/4451
Comment From: zunzunwang
Hello Guys, Some similar info related to this topic. When I just change all @Tool to @McpTool in the module weather/starter-webflux-server (add spring.ai.mcp.server.type=ASYNC at application.properties) I get this error and can't event start the server:
2025-09-25T03:44:36.554+02:00 ERROR 90979 --- [           main] o.s.boot.SpringApplication               : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'weatherTools' defined in org.springframework.ai.mcp.sample.server.McpServerApplication: Failed to instantiate [org.springframework.ai.tool.ToolCallbackProvider]: Factory method 'weatherTools' threw exception with message: No @Tool annotated methods found in org.springframework.ai.mcp.sample.server.WeatherService@37b52340.Did you mean to pass a ToolCallback or ToolCallbackProvider? If so, you have to use .toolCallbacks() instead of .tool()
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:657) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:645) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1367) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1197) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1221) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1187) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1122) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) ~[spring-context-6.2.6.jar:6.2.6]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) ~[spring-context-6.2.6.jar:6.2.6]
    at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[spring-boot-3.4.5.jar:3.4.5]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:753) ~[spring-boot-3.4.5.jar:3.4.5]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.4.5.jar:3.4.5]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.4.5.jar:3.4.5]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1362) ~[spring-boot-3.4.5.jar:3.4.5]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1351) ~[spring-boot-3.4.5.jar:3.4.5]
    at org.springframework.ai.mcp.sample.server.McpServerApplication.main(McpServerApplication.java:15) ~[classes/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.ai.tool.ToolCallbackProvider]: Factory method 'weatherTools' threw exception with message: No @Tool annotated methods found in org.springframework.ai.mcp.sample.server.WeatherService@37b52340.Did you mean to pass a ToolCallback or ToolCallbackProvider? If so, you have to use .toolCallbacks() instead of .tool()
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:199) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiateWithFactoryMethod(SimpleInstantiationStrategy.java:88) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:168) ~[spring-beans-6.2.6.jar:6.2.6]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-6.2.6.jar:6.2.6]
    ... 21 common frames omitted
Caused by: java.lang.IllegalStateException: No @Tool annotated methods found in org.springframework.ai.mcp.sample.server.WeatherService@37b52340.Did you mean to pass a ToolCallback or ToolCallbackProvider? If so, you have to use .toolCallbacks() instead of .tool()
    at org.springframework.ai.tool.method.MethodToolCallbackProvider.assertToolAnnotatedMethodsPresent(MethodToolCallbackProvider.java:77) ~[spring-ai-model-1.1.0-20250923.001833-516.jar:1.1.0-SNAPSHOT]
    at org.springframework.ai.tool.method.MethodToolCallbackProvider.<init>(MethodToolCallbackProvider.java:61) ~[spring-ai-model-1.1.0-20250923.001833-516.jar:1.1.0-SNAPSHOT]
    at org.springframework.ai.tool.method.MethodToolCallbackProvider$Builder.build(MethodToolCallbackProvider.java:153) ~[spring-ai-model-1.1.0-20250923.001833-516.jar:1.1.0-SNAPSHOT]
    at org.springframework.ai.mcp.sample.server.McpServerApplication.weatherTools(McpServerApplication.java:20) ~[classes/:na]
    at org.springframework.ai.mcp.sample.server.McpServerApplication$$SpringCGLIB$$0.CGLIB$weatherTools$1(<generated>) ~[classes/:na]
    at org.springframework.ai.mcp.sample.server.McpServerApplication$$SpringCGLIB$$FastClass$$1.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[spring-core-6.2.6.jar:6.2.6]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:393) ~[spring-context-6.2.6.jar:6.2.6]
    at org.springframework.ai.mcp.sample.server.McpServerApplication$$SpringCGLIB$$0.weatherTools(<generated>) ~[classes/:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.2.6.jar:6.2.6]
    ... 24 common frames omitted
I need to keep at least one @Tool in my service then I can see the other tools announced by @McpTool
any idea about it ? thanks in advanced
Comment From: YunKuiLu
@zunzunwang  When you only use @McpTool, you no longer need to manually create the ToolCallbackProvider Bean, so you can delete it.
@SpringBootApplication
public class McpServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(McpServerApplication.class, args);
    }
    // @Bean
    // public ToolCallbackProvider weatherTools(WeatherService weatherService) {
    //  return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
    // }
}
Comment From: zunzunwang
@zunzunwang When you only use
@McpTool, you no longer need to manually create the ToolCallbackProvider Bean, so you can delete it.``` @SpringBootApplication public class McpServerApplication {
public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); }
// @Bean // public ToolCallbackProvider weatherTools(WeatherService weatherService) { // return MethodToolCallbackProvider.builder().toolObjects(weatherService).build(); // }
} ```
Hi @YunKuiLu It works. thanks a lot for your help!
Comment From: rnett
Regardless of the final approach chosen (i.e. wrap or exclude), I think silently ignoring incorrectly-typed methods is not the way to go. If it was an error, or at least a warning, it would be significantly less of a footgun.