Skip to content

Commit 77ebdff

Browse files
committed
README: format
Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
1 parent 07995a5 commit 77ebdff

File tree

1 file changed

+138
-21
lines changed

1 file changed

+138
-21
lines changed

README.md

Lines changed: 138 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ spring.ai.mcp.server.protocol=STREAMABLE
6666
Then, configure the security for your project in the usual Spring-Security way, adding the provided configurer.
6767
Create a configuration class, and reference the authorization server's URI.
6868
In 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

106107
It 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
114116
class 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
204209
WebClient-based clients (from `spring-ai-starter-mcp-client-webflux`).
205210
This 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`.
311316
In that case, you can expose a bean of type `McpSyncHttpClientRequestCustomizer`.
312317
Depending 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,127 @@ 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+
//
470+
// For WebClient based clients
471+
@Bean
472+
McpSyncClient client(
473+
WebClient.Builder mcpWebClientBuilder,
474+
ObjectMapper objectMapper,
475+
McpClientCommonProperties commonProperties
476+
) {
477+
var builder = mcpWebClientBuilder.baseUrl(mcpServerUrl);
478+
var transport = WebClientStreamableHttpTransport.builder(builder)
479+
.jsonMapper(new JacksonMcpJsonMapper(objectMapper))
480+
.build();
481+
482+
var clientInfo = new McpSchema.Implementation("clientName", commonProperties.getVersion());
483+
484+
return McpClient.sync(transport)
485+
.clientInfo(clientInfo)
486+
.requestTimeout(commonProperties.getRequestTimeout())
487+
.transportContextProvider(new AuthenticationMcpTransportContextProvider())
488+
.build();
489+
}
490+
```
491+
492+
You can then add it to the tools available to a chat client:
493+
494+
```java
495+
var chatResponse = chatClient.prompt("Prompt the LLM to _do the thing_")
496+
.toolCallbacks(new SyncMcpToolCallbackProvider(mcpClient1, mcpClient2, mcpClient3))
497+
.call()
498+
.content();
499+
```
500+
386501
### Known limitations
387502

388503
- Spring WebFlux servers are not supported.
389504
- Spring AI autoconfiguration initializes the MCP client app start.
390-
Most MCP servers want require calls to be authenticated with a token.
505+
Most MCP servers want calls to be authenticated with a token, so you
506+
need to work around the Spring AI auto-config ([see the workaround above](#work-around-spring-ai-autoconfiguration))
391507

392508
Note:
393509

@@ -407,6 +523,7 @@ It provides a simple configurer for an MCP server.
407523
*Maven*
408524

409525
```xml
526+
410527
<dependency>
411528
<groupId>org.springaicommunity</groupId>
412529
<artifactId>mcp-authorization-server</artifactId>

0 commit comments

Comments
 (0)