Клиент Spring MCP не имеет доступа к заголовкам HTTP из McpTransportContext в потоковом режимеJAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Клиент Spring MCP не имеет доступа к заголовкам HTTP из McpTransportContext в потоковом режиме

Сообщение Anonymous »

Я пытаюсь реализовать Клиент Spring MCP на основе библиотеки Spring-ai 1.1.0-SNAPSHOT.
Минимальный исходный код проекта доступен в этом общедоступном репозитории GitHub: https://github.com/robynico/spring-mcp-client-stream.
Контроллер Spring предоставляет 2 конечные точки:
  • /chat → Непотоковая передача
  • /stream-chat → Потоковая передача
При вызове /stream-chat ServletContext не доступен из McpTransportContext, несмотря на реализацию AuthenticationMcpTransportContextProvider, как рекомендовано в: https://github.com/spring-ai-community/mcp-security
Вопрос:

Как передавать HTTP-заголовки потокобезопасным способом в контекст выполнения инструмента MCP при использовании потоковой передачи с реактором?

Контроллер
ChatController.java
@RestController
@Validated
public class ChatController {

public static final String HTTP_HEADER_TENANT = "Tenant";

private static final Logger logger = LoggerFactory.getLogger(ChatController.class);

private final AgentService agentService;

public ChatController(AgentService agentService) {
this.agentService = agentService;
}

@PostMapping(value = "/chat")
public String chat(@RequestBody @Valid ChatRequest request) {
return agentService.chat(request.getPrompt());
}

@PostMapping(value = "/stream-chat")
public Flux streamChat(@RequestBody @Valid ChatRequest request, @Headers HttpHeaders headers) {
var requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
logger.info("Tenant {}", requestAttributes.getRequest().getHeader(HTTP_HEADER_TENANT));
return agentService.streamChat(request.getPrompt());
}

}

Поставщик контекста и настройщик запросов
McpConfiguration.java
@Configuration
public class McpConfiguration implements WebMvcConfigurer {

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

@Bean
McpSyncHttpClientRequestCustomizer requestCustomizer() {
return new CustomMcpSyncRequestCustomizer();
}

}

AuthenticationMcpTransportContextProvider.java
public class AuthenticationMcpTransportContextProvider implements Supplier {

private static final Logger logger = LoggerFactory.getLogger(AuthenticationMcpTransportContextProvider.class);

public static final String HTTP_HEADERS_KEY = "httpHeaders";

@Override
public McpTransportContext get() {
var data = new HashMap();
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
if (previousAttributes instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes) previousAttributes).getRequest();
Map headers = new HashMap();
List.of(HttpHeaders.AUTHORIZATION, HTTP_HEADER_TENANT).forEach(headerName -> {
String value = request.getHeader(headerName);
if (value != null) {
headers.put(headerName, value);
}
});
data.put(HTTP_HEADERS_KEY, headers);
}
logger.info("Headers found in RequestContextHolder: {}", data);
return McpTransportContext.create(data);
}

}

CustomMcpSyncRequestCustomizer.java
class CustomMcpSyncRequestCustomizer implements McpSyncHttpClientRequestCustomizer {

private static final Logger logger = LoggerFactory.getLogger(CustomMcpSyncRequestCustomizer.class);

@Override
public void customize(HttpRequest.Builder builder, String method, URI endpoint, String body,
McpTransportContext context) {
var headers = context.get(AuthenticationMcpTransportContextProvider.HTTP_HEADERS_KEY);
logger.info("Headers found in McpTransportContext: {}", headers);

if (headers instanceof Map headerMap) {
List.of(HttpHeaders.AUTHORIZATION, HTTP_HEADER_TENANT).forEach(headerName -> {
var value = headerMap.get(headerName);
if (value instanceof String stringValue) {
builder.header(headerName, stringValue);
}
});
}
}

}

Клиент чата
AgentService.java
@Component
public class AgentService {

private final ChatClient chatClient;

public AgentService(ChatClient.Builder chatClientBuilder, ToolCallbackProvider mcpToolProvider) {
var options = BedrockChatOptions.builder()
.model("anthropic.claude-3-5-sonnet-20240620-v1:0")
.temperature(0.6)
.maxTokens(3000)
.build();
chatClient = chatClientBuilder.defaultOptions(options).defaultToolCallbacks(mcpToolProvider).build();
}

public Flux streamChat(String prompt) {
return chatClient.prompt().user(userMessage -> userMessage.text(prompt)).stream().content();
}

public String chat(String prompt) {
return chatClient.prompt().user(userMessage -> userMessage.text(prompt)).call().content();
}

}


Тест
Curl:
curl -X POST http://localhost:8080/stream-chat \
-H "Content-Type: application/json" \
-H "Tenant: A " \
-d '{"prompt": "do an echo toto"}'

Наблюдаемые журналы:
[nio-8080-exec-3] c.c.a.m.c.controller.ChatController : Tenant A
[yEventLoop-1-14] o.s.a.b.converse.BedrockProxyChatModel : Completed streaming response.
[oundedElastic-2] uthenticationMcpTransportContextProvider : Headers found in RequestContextHolder: {}
[oundedElastic-2] c.a.m.c.c.CustomMcpSyncRequestCustomizer : Headers found in McpTransportContext: null
[yEventLoop-1-14] o.s.a.b.converse.BedrockProxyChatModel : Completed streaming response.


Подробнее здесь: https://stackoverflow.com/questions/797 ... xt-in-stre
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»