@@ -66,7 +66,8 @@ spring.ai.mcp.server.protocol=STREAMABLE
6666Then, configure the security for your project in the usual Spring-Security way, adding the provided configurer.
6767Create a configuration class, and reference the authorization server's URI.
6868In this example, we have set the authz server's issuer URI in the well known Spring property
69- ` spring.security.oauth2.resourceserver.jwt.issuer-uri ` . This property is not a requirement.
69+ ` spring.security.oauth2.resourceserver.jwt.issuer-uri ` .
70+ Using this exact name is not a requirement, and you may use a custom property.
7071
7172``` java
7273
@@ -90,7 +91,7 @@ class McpServerConfiguration {
9091 mcpAuthorization. authorizationServer(issuerUrl);
9192 // OPTIONAL: enforce the `aud` claim in the JWT token.
9293 // Not all authorization servers support resource indicators,
93- // so it may be absent.
94+ // so it may be absent. Defaults to `false`.
9495 // See RFC 8707 Resource Indicators for OAuth 2.0
9596 // https://www.rfc-editor.org/rfc/rfc8707.html
9697 mcpAuthorization. validateAUdienceClaim(true );
@@ -104,13 +105,14 @@ class McpServerConfiguration {
104105### Special case: only secure tool calls
105106
106107It is also possible to secure the tools only, and not the rest of the MCP Server. For example, both ` initialize ` and
107- ` tools/list ` are made public, but ` tools/call ` is authenticated. To enable this, update the security configuration:
108+ ` tools/list ` are made public, but ` tools/call ` is authenticated.
109+ To enable this, update the security configuration, turn on method security and requests to ` /mcp ` are allowed:
108110
109111``` java
110112
111113@Configuration
112114@EnableWebSecurity
113- @EnableMethodSecurity // ⚠ enable annotation-driven security
115+ @EnableMethodSecurity // ⬅️ enable annotation-driven security
114116class McpServerConfiguration {
115117
116118 @Value (" ${spring.security.oauth2.resourceserver.jwt.issuer-uri}" )
@@ -119,9 +121,12 @@ class McpServerConfiguration {
119121 @Bean
120122 SecurityFilterChain securityFilterChain (HttpSecurity http ) throws Exception {
121123 return http
122- // Open every request on the server
123- .authorizeHttpRequests(auth - > auth. anyRequest(). permitAll())
124- // ⚠ Configure OAuth2 on the MCP server
124+ // ⬇️ Open every request on the server
125+ .authorizeHttpRequests(auth - > {
126+ auth. requestMatcher(" /mcp" ). permitAll();
127+ auth. anyRequest(). authenticated();
128+ })
129+ // Configure OAuth2 on the MCP server
125130 .with(
126131 McpResourceServerConfigurer . mcpServerAuthorization(),
127132 (mcpAuthorization) - > {
@@ -196,11 +201,11 @@ public String greet(
196201 or [ stateless transport] ( https://modelcontextprotocol.io/sdk/java/mcp-server#stateless-streamable-http-webmvc ) . (the
197202 link for stateless does not work out of the box, reload the page if required)
198203- WebFlux-based servers are not supported.
199- - Opaque tokens are not supported. Please use JWT.
204+ - Opaque tokens are not supported. Use JWT.
200205
201206## MCP Client Security
202207
203- Provide OAuth 2 support for MCP clients, with both HttpClient-based clients (from ` spring-ai-starter-mcp-client ` ) and
208+ Provides OAuth 2 support for MCP clients, with both HttpClient-based clients (from ` spring-ai-starter-mcp-client ` ) and
204209WebClient-based clients (from ` spring-ai-starter-mcp-client-webflux ` ).
205210This module supports ` McpSyncClient ` s only.
206211
@@ -241,10 +246,10 @@ For our MCP clients, there are three flows available for obtaining tokens:
241246🤔 Which flow should I use?
242247
243248- If there are user-level permission, AND you know every MCP request will be made within the context of a user request
244- (such as: adding tools manually in the GUI ), then use the ` authorization_code ` flow, with either
249+ (ensure there are not ` tools/list ` call no app startup ), then use the ` authorization_code ` flow, with either
245250 ` OAuth2AuthorizationCodeSyncHttpRequestCustomizer ` or ` McpOAuth2AuthorizationCodeExchangeFilterFunction ` .
246251- If there are no user-level permissions, and you want to secure "client-to-server" communication with an access token,
247- use the ` client_credentials ` flow, either with ` OAuth2ClientCredentialsSyncHttpRequestCustomizer ` or
252+ use the ` client_credentials ` flow, with either ` OAuth2ClientCredentialsSyncHttpRequestCustomizer ` or
248253 ` McpOAuth2ClientCredentialsExchangeFilterFunction ` .
249254- If there are user-level permission, AND you configure your MCP clients using Spring Boot properties (such as
250255 ` spring.ai.mcp.client.streamable-http.connections.<server-name>.url=<server-url> ` ), then, on application startup,
@@ -306,7 +311,7 @@ If you already have a filter chain configured, ensure that `.oauth2Client(...)`
306311
307312### Use with ` spring-ai-starter-mcp-client `
308313
309- When using ` spring-ai-starter-mcp-client ` , the underlying MCP client transport will be based on the Java SDK 's
314+ When using ` spring-ai-starter-mcp-client ` , the underlying MCP client transport will be based on the JDK 's
310315` HttpClient ` .
311316In that case, you can expose a bean of type ` McpSyncHttpClientRequestCustomizer ` .
312317Depending on your [ authorization flow] ( #authorization-flows ) of choice, you may use one of the following
@@ -330,15 +335,22 @@ class McpConfiguration {
330335
331336 @Bean
332337 McpSyncClientCustomizer syncClientCustomizer () {
333- return (name, syncSpec) - > syncSpec. transportContextProvider(new AuthenticationMcpTransportContextProvider ());
338+ return (name, syncSpec) - >
339+ syncSpec. transportContextProvider(
340+ new AuthenticationMcpTransportContextProvider ()
341+ );
334342 }
335343
336344 @Bean
337345 McpSyncHttpClientRequestCustomizer requestCustomizer (
338- OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager ,
339- ClientRegistrationRepository clientRegistrationRepository ) {
340- // The clientRegistration name, "authserver", must match the name in application.properties
341- return new OAuth2AuthorizationCodeSyncHttpRequestCustomizer (oAuth2AuthorizedClientManager, " authserver" );
346+ OAuth2AuthorizedClientManager clientManager
347+ ) {
348+ // The clientRegistration name, "authserver",
349+ // must match the name in application.properties
350+ return new OAuth2AuthorizationCodeSyncHttpRequestCustomizer (
351+ clientManager,
352+ " authserver"
353+ );
342354 }
343355
344356}
@@ -371,23 +383,126 @@ class McpConfiguration {
371383
372384 @Bean
373385 McpSyncClientCustomizer syncClientCustomizer () {
374- return (name, syncSpec) - > syncSpec. transportContextProvider(new AuthenticationMcpTransportContextProvider ());
386+ return (name, syncSpec) - >
387+ syncSpec. transportContextProvider(
388+ new AuthenticationMcpTransportContextProvider ()
389+ );
375390 }
376391
377392 @Bean
378393 WebClient .Builder mcpWebClientBuilder (OAuth2AuthorizedClientManager clientManager ) {
379394 // The clientRegistration name, "authserver", must match the name in application.properties
380- return WebClient . builder()
381- .filter(new McpOAuth2AuthorizationCodeExchangeFilterFunction (clientManager, " authserver" ));
395+ return WebClient . builder(). filter(
396+ new McpOAuth2AuthorizationCodeExchangeFilterFunction (
397+ clientManager,
398+ " authserver"
399+ )
400+ );
382401 }
383402}
384403```
385404
405+ ### Work around Spring AI autoconfiguration
406+
407+ Spring AI integrates MCP tools as if they were regular "tools" (e.g. ` @Tool ` methods).
408+ As such, they are discovered when application starts up.
409+ This means that any MCP client that is configured through configuration properties, such
410+ as ` spring.ai.mcp.client.streamable-http.connections.<SERVER-NAME>.url=... ` will be initialized.
411+ In practice, there will be multiple calls issued to the MCP Server (` initialize ` followed by ` tools/list ` ).
412+ The server will require a token for these calls, and, without a user present, this is an issue in the general case.
413+ There are a few ways around this:
414+
415+ ** Disable the @Tool auto-configuration**
416+
417+ You can turn off Spring AI's ` @Tool ` autoconfiguration altogether.
418+ This will disable all method and function-based tool calling, and only MCP tools will be available.
419+ The easiest way to do so is to publish an empty ` ToolCallbackResolver ` bean:
420+
421+ ``` java
422+
423+ @Configuration
424+ public class McpConfiguration {
425+
426+ @Bean
427+ ToolCallbackResolver resolver () {
428+ return new StaticToolCallbackResolver (List . of());
429+ }
430+
431+ }
432+ ```
433+
434+ ** Programmatically configure MCP clients**
435+
436+ You may also forego Spring AI's autoconfiguration altogether, and create the MCP clients programmatically.
437+ The easiest way is to draw some inspiration on the transport
438+ auto-configurations ([ HttpClient] ( https://github.com/spring-projects/spring-ai/blob/main/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/main/java/org/springframework/ai/mcp/client/httpclient/autoconfigure/StreamableHttpHttpClientTransportAutoConfiguration.java ) , [ WebClient] ( https://github.com/spring-projects/spring-ai/blob/main/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/main/java/org/springframework/ai/mcp/client/webflux/autoconfigure/StreamableHttpWebFluxTransportAutoConfiguration.java ) )
439+ as well as
440+ the [ client auto-configuration] ( https://github.com/spring-projects/spring-ai/blob/main/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/McpClientAutoConfiguration.java ) .
441+
442+ All in all, it could look like so:
443+
444+ ``` java
445+ // For HttpClient-based clients
446+ @Bean
447+ McpSyncClient client(
448+ ObjectMapper objectMapper,
449+ McpSyncHttpClientRequestCustomizer requestCustomizer,
450+ McpClientCommonProperties commonProps
451+ ) {
452+ var transport = HttpClientStreamableHttpTransport . builder(mcpServerUrl)
453+ .clientBuilder(HttpClient . newBuilder())
454+ .jsonMapper(new JacksonMcpJsonMapper (objectMapper))
455+ .httpRequestCustomizer(requestCustomizer)
456+ .build();
457+
458+ var clientInfo = new McpSchema .Implementation (" client-name" , commonProps. getVersion());
459+
460+ return McpClient . sync(transport)
461+ .clientInfo(clientInfo)
462+ .requestTimeout(commonProps. getRequestTimeout())
463+ .transportContextProvider(new AuthenticationMcpTransportContextProvider ())
464+ .build();
465+ }
466+
467+ // -------------------------
468+
469+ // For WebClient based clients
470+ @Bean
471+ McpSyncClient client(
472+ WebClient . Builder mcpWebClientBuilder,
473+ ObjectMapper objectMapper,
474+ McpClientCommonProperties commonProperties
475+ ) {
476+ var builder = mcpWebClientBuilder. baseUrl(mcpServerUrl);
477+ var transport = WebClientStreamableHttpTransport . builder(builder)
478+ .jsonMapper(new JacksonMcpJsonMapper (objectMapper))
479+ .build();
480+
481+ var clientInfo = new McpSchema .Implementation (" clientName" , commonProperties. getVersion());
482+
483+ return McpClient . sync(transport)
484+ .clientInfo(clientInfo)
485+ .requestTimeout(commonProperties. getRequestTimeout())
486+ .transportContextProvider(new AuthenticationMcpTransportContextProvider ())
487+ .build();
488+ }
489+ ```
490+
491+ You can then add it to the tools available to a chat client:
492+
493+ ``` java
494+ var chatResponse = chatClient. prompt(" Prompt the LLM to _do the thing_" )
495+ .toolCallbacks(new SyncMcpToolCallbackProvider (mcpClient1, mcpClient2, mcpClient3))
496+ .call()
497+ .content();
498+ ```
499+
386500### Known limitations
387501
388502- Spring WebFlux servers are not supported.
389503- Spring AI autoconfiguration initializes the MCP client app start.
390- Most MCP servers want require calls to be authenticated with a token.
504+ Most MCP servers want calls to be authenticated with a token, so you
505+ need to work around the Spring AI auto-config ([ see the workaround above] ( #work-around-spring-ai-autoconfiguration ) )
391506
392507Note:
393508
@@ -407,6 +522,7 @@ It provides a simple configurer for an MCP server.
407522* Maven*
408523
409524``` xml
525+
410526<dependency >
411527 <groupId >org.springaicommunity</groupId >
412528 <artifactId >mcp-authorization-server</artifactId >
0 commit comments