@@ -507,6 +507,118 @@ class McpConfiguration {
507507}
508508```
509509
510+ ### Customize HTTP requests beyond MCP Security's OAuth2 support
511+
512+ MCP Security's default client support integrates with Spring Security to add OAuth2 support. Essentially, it gets a
513+ token on behalf of the user, and modifies the HTTP request from the Client to the Server, adding that token in an
514+ Authorization header.
515+
516+ If you'd like to modify HTTP requests beyond what MCP Security provide, you can create your own
517+ ` McpSyncHttpClientRequestCustomizer ` or ` ExchangeFilterFunction ` .
518+
519+ For HTTP clients:
520+
521+ ``` java
522+
523+ @Configuration
524+ class McpConfiguration {
525+
526+ @Bean
527+ McpSyncHttpClientRequestCustomizer requestCustomizer () {
528+ return (builder, method, endpoint, body, context) - >
529+ builder
530+ .header(" x-custom-header" , " custom-value" )
531+ .header(" x-life-the-universe-everything" , " 42" );
532+ }
533+
534+ }
535+ ```
536+
537+ For web clients:
538+
539+ ``` java
540+
541+ @Configuration
542+ class McpConfiguration {
543+
544+ @Bean
545+ WebClient .Builder mcpWebClientBuilder () {
546+ return WebClient . builder(). filter((request, next) - > {
547+ var newRequest = ClientRequest . from(request)
548+ .header(" x-custom-header" , " custom-value" )
549+ .header(" x-life-the-universe-everything" , " 42" )
550+ .build();
551+ return next. exchange(newRequest);
552+ });
553+ }
554+
555+ }
556+ ```
557+
558+ There is no way to guarantee on which thread these request customizers will run.
559+ As such, thread-locals are not available in these lambda functions.
560+ If you would like to use thread-locals in this context, use a ` McpTransportContextProvider ` bean.
561+ It can extract thread-locals and make them available in an ` McpTransportContext ` object.
562+
563+ For HttpClient-based request customizers, the ` McpTransportContext ` will be available in the ` customize ` method. See, for example, with a Sync client (async works similarly):
564+
565+ ``` java
566+
567+ @Configuration
568+ class McpConfiguration {
569+
570+ @Bean
571+ McpSyncClientCustomizer syncClientCustomizer () {
572+ return (name, syncSpec) - > syncSpec. transportContextProvider(() - > {
573+ var myThing = MyThreadLocalThing . get();
574+ return McpTransportContext . create(Map . of(" custom-key" , myThing));
575+ });
576+ }
577+
578+ @Bean
579+ McpSyncHttpClientRequestCustomizer requestCustomizer () {
580+ return (builder, method, endpoint, body, context) - >
581+ builder. header(" x-custom-header" , context. get(" custom-key" ));
582+ }
583+
584+ }
585+ ```
586+
587+ For WebClient-based filter functions, the ` McpTransportContext ` will be available in the Reactor context, under
588+ ` McpTransportContext.KEY ` :
589+
590+ ``` java
591+
592+ @Configuration
593+ class McpConfiguration {
594+
595+ @Bean
596+ McpSyncClientCustomizer syncClientCustomizer () {
597+ return (name, syncSpec) - > syncSpec. transportContextProvider(() - > {
598+ var myThing = MyThreadLocalThing . get();
599+ return McpTransportContext . create(Map . of(" custom-key" , myThing));
600+ });
601+ }
602+
603+ @Bean
604+ WebClient .Builder mcpWebClientBuilder () {
605+ return WebClient . builder()
606+ .filter((request, next) - >
607+ Mono . deferContextual(reactorCtx - > {
608+ var transportCtx = reactorCtx. get(McpTransportContext . class);
609+ String customThing = transportCtx. get(" custom-key" ). toString();
610+ var newRequest = ClientRequest . from(request)
611+ .header(" x-custom-header" , customThing)
612+ .build();
613+
614+ return next. exchange(newRequest);
615+ })
616+ );
617+ }
618+
619+ }
620+ ```
621+
510622### Work around Spring AI autoconfiguration
511623
512624Spring AI integrates MCP tools as if they were regular "tools" (e.g. ` @Tool ` methods).
0 commit comments