Commit af66bcf9 authored by Kostis Trantzas's avatar Kostis Trantzas
Browse files

Merge branch 'develop' into 'main'

Hotfix #7: MCP Backend authentication with MCP Server

See merge request !8
parents a6801ecf abbf16ef
Loading
Loading
Loading
Loading
Loading
+40 −32
Original line number Diff line number Diff line
package org.etsi.osl.mcp.backend.configuration;

import org.springaicommunity.mcp.security.client.sync.AuthenticationMcpTransportContextProvider;
import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;

/**
 * @author Daniel Garnier-Moiroux
 * MCP Client Configuration for JWT Bearer Token Propagation
 * 
 * Configures the MCP client to forward JWT Bearer tokens from incoming API requests
 * to the MCP Server. This enables proper authorization when calling MCP Server tools
 * that require authentication.
 * 
 * @author OpenSlice Team
 */
@Configuration
class McpConfiguration {

    // Disabled OAuth2 customization for MCP client - not needed for JWT-based API
    // The MCP server connection doesn't need OAuth2 authentication
    
    // @Bean
    // McpSyncHttpClientRequestCustomizer requestCustomizer(OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager,
    //         ClientRegistrationRepository clientRegistrationRepository) {
    //     var registrationId = findUniqueClientRegistration(clientRegistrationRepository);
    //     return new OAuth2AuthorizationCodeSyncHttpRequestCustomizer(oAuth2AuthorizedClientManager, registrationId);
    // }

    // @Bean
    // McpSyncClientCustomizer syncClientCustomizer() {
    //     return (name, syncSpec) -> syncSpec.transportContextProvider(new AuthenticationMcpTransportContextProvider());
    // }

    /**
     * Returns the ID of the {@code spring.security.oauth2.client.registration}, if
     * unique.
     * Configures JWT Bearer token propagation to MCP Server.
     * 
     * When the MCP Backend receives an authenticated request with a JWT Bearer token
     * (e.g., from Postman or other REST clients), this customizer extracts that token
     * and forwards it to all MCP Server HTTP calls, ensuring proper authorization.
     */
    private static String findUniqueClientRegistration(ClientRegistrationRepository clientRegistrationRepository) {
        String registrationId;
        if (!(clientRegistrationRepository instanceof InMemoryClientRegistrationRepository repo)) {
            throw new IllegalStateException("Expected an InMemoryClientRegistrationRepository");
    @Bean
    McpSyncHttpClientRequestCustomizer requestCustomizer() {
        return (builder, connectionName, serverUri, endpoint, context) -> {
            // Get the current authentication from SecurityContext
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            
            // Check if the authentication is JWT-based (from Bearer token)
            if (authentication instanceof JwtAuthenticationToken jwtAuth) {
                // Extract the JWT token value
                String tokenValue = jwtAuth.getToken().getTokenValue();
                
                // Add as Bearer token to the outgoing MCP Server request
                builder.header("Authorization", "Bearer " + tokenValue);
            }
        var iterator = repo.iterator();
        var firstRegistration = iterator.next();
        if (iterator.hasNext()) {
            throw new IllegalStateException("Expected a single Client Registration");
        };
    }
        registrationId = firstRegistration.getRegistrationId();
        return registrationId;

    @Bean
    McpSyncClientCustomizer syncClientCustomizer() {
        return (name, syncSpec) -> syncSpec.transportContextProvider(new AuthenticationMcpTransportContextProvider());
    }

}
 No newline at end of file