spring-ai icon indicating copy to clipboard operation
spring-ai copied to clipboard

Support accessing HTTP headers in ToolContext when using WebFlux

Open 18801151992 opened this issue 9 months ago • 28 comments

Please do a quick search on GitHub issues first, the feature you are about to request might have already been requested.

Expected Behavior

When using Spring AI (1.0.0-M7) with WebFlux, the ToolContext should include HTTP headers from the incoming request, so that they can be accessed within a @Tool method. @Tool public String example(String input, ToolContext toolContext) { String token = toolContext.get("X-Token", String.class); return "Token: " + token; } This would allow developers to use authentication tokens or other metadata from headers during tool execution, without requiring custom logic.

Current Behavior

In the current version (M7), when running on WebFlux, the ToolContext does not include HTTP headers, unlike what some might expect based on MVC or previous versions.

Because WebFlux is reactive and does not use RequestContextHolder, there is no easy built-in way to access headers from within a @Tool method.

Attempts to extract headers using ToolContext.get(...) return null.

Context

This affects any project using Spring AI with spring-ai-starter-mcp-server-webflux.

In my use case, I need to extract an authentication token from headers like X-Token or Authorization to perform access control logic within tools.

I considered overriding the tool execution pipeline, but SyncMcpToolCallbackProvider does not expose a customizable invokeTool() method.

A possible workaround is to write a wrapper controller that extracts headers and manually adds them to ToolContext, but this breaks the built-in tool invocation pattern.

Please consider adding support for automatically injecting headers into ToolContext for WebFlux-based deployments, or documenting the official workaround if available.

Thank you!

18801151992 avatar Apr 16 '25 02:04 18801151992

Is there any solution available now? I am also facing this issue.

jeweis avatar Apr 16 '25 03:04 jeweis

I have this problem too

zz-zhi54 avatar Apr 17 '25 09:04 zz-zhi54

It seems that the current MCP SDKS all have similar problems. I tried to use the python sdk to obtain the headers, but it didn't work either.

zxypro1 avatar Apr 18 '25 03:04 zxypro1

It seems that the current MCP SDKS all have similar problems. I tried to use the python sdk to obtain the headers, but it didn't work either.

python其实是可以解决的,你在查查gpt吧

18801151992 avatar Apr 21 '25 02:04 18801151992

https://github.com/modelcontextprotocol/java-sdk/pull/172 If this gets merged, it might help.

los-ko avatar Apr 23 '25 10:04 los-ko

+1 Without getting the authenticated user information from acces token, ID token, or headers, a (remote) MCP server does not make a lot of sense.

jochenchrist avatar Apr 23 '25 17:04 jochenchrist

Not only WebFlux, but WebMVC is also not supported. I use below code to get header, it is not working:

// Get current request using RequestContextHolder
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    String token= null;
    
    if (attributes != null) {
        HttpServletRequest request = attributes.getRequest();
        token= request.getHeader("token");
        logger.info("Received token: {}", token);
    } else {
        logger.warn("No request context available");
    }

heavenwing avatar Apr 24 '25 07:04 heavenwing

@heavenwing There is no problem with webmvc, at least there is no problem here. You can check whether the tool and controller are in the same thread.

zz-zhi54 avatar Apr 24 '25 07:04 zz-zhi54

@heavenwing WebMVC also does not provide built-in support and requires external plugins to handle thread-related issues. This problem can be resolved by using Alibaba's thread plugin.

18801151992 avatar Apr 24 '25 08:04 18801151992

MVC is supported. There is no place in the tool that uses a new thread. You can get the Header from RequestContextHolder.getRequestAttributes(). My project is already running。Flux is a different thread

zz-zhi54 avatar Apr 24 '25 08:04 zz-zhi54

@poo0054 I don't understand this sentence "You can check whether the tool and controller are in the same thread." My code doesn't have any controller, just SpringBootApplication contains below Bean

	@Bean
	public ToolCallbackProvider materialTools(MaterialService materialService) {
		return MethodToolCallbackProvider.builder().toolObjects(materialService).build();
	}

And MaterialService have a Tool method, I try to get request header in this method, but RequestContextHolder.getRequestAttributes() return null

heavenwing avatar Apr 24 '25 09:04 heavenwing

@heavenwing

Image

Can you provide a minimal example?

zz-zhi54 avatar Apr 24 '25 09:04 zz-zhi54

@heavenwing

Image

Can you provide a minimal example?

hao can i use RequestContextHolder in @Tool

lghgf123 avatar Apr 24 '25 10:04 lghgf123

这个问题可以关了,已经有解决方法了。使用 BiFunction<BiFunctionEvents.Request, ToolContext, String>即可。 现在社区人好少,感觉没什么人关注,我们聊了大半天都没人搭理我们。

This issue can be closed as there's already a solution. Just use BiFunction<BiFunctionEvents.Request, ToolContext, String>. There aren't many people in the community these days. It seems that few people are paying attention. We've been chatting for a long time but no one has responded to us.

Image


    @Bean
    public FunctionToolCallback biFunctionEvents() {
        return FunctionToolCallback.builder("getTime", new BiFunctionEvents())
                .description("获取时间")
                .inputType(BiFunctionEvents.Request.class)
                .build();
    }

    @Autowired
    public void setChatClientBuilder(ChatClient.Builder chatClientBuilder,
                                     FunctionCallback biFunctionEvents) {
        this.chatClient = chatClientBuilder
                .clone()
                .defaultTools(biFunctionEvents)
                .defaultAdvisors(new SimpleLoggerAdvisor(Ordered.LOWEST_PRECEDENCE - 1))
                .build();
    }

 @GetMapping("/quality")
    public Flux<String> quality(@RequestParam("message") String message) {
        UserMessage userMessage = new UserMessage(message);
        String format = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
        LogMessage logMessage = LogMessage.format("今天是日期为:[%s]。", format);
        ChatClient.StreamResponseSpec stream = chatClient.prompt()
                .messages(userMessage)
                .toolContext(Map.of("format", format))
                .system(logMessage.toString())
                .stream();
        return stream.content();
    }


public class BiFunctionEvents implements BiFunction<BiFunctionEvents.Request, ToolContext, String> {

    @Override
    public String apply(@ToolParam(description = "位置信息") Request location, ToolContext toolContext) {
        System.out.println(toolContext);
        return toolContext.getContext().get("format").toString();
    }

    public record Request(@ToolParam(description = "位置信息") String location) {
    }
}

zz-zhi54 avatar Apr 24 '25 10:04 zz-zhi54

Image

Source code is here

  • FunctionToolCallback#call(String toolInput, @Nullable ToolContext toolContext)
	@Override
	public String call(String toolInput, @Nullable ToolContext toolContext) {
		Assert.hasText(toolInput, "toolInput cannot be null or empty");

		logger.debug("Starting execution of tool: {}", toolDefinition.name());

		I request = JsonParser.fromJson(toolInput, toolInputType);
		O response = toolFunction.apply(request, toolContext);

		logger.debug("Successful execution of tool: {}", toolDefinition.name());

		return toolCallResultConverter.convert(response, null);
	}

zz-zhi54 avatar Apr 24 '25 10:04 zz-zhi54

Image

Source code is here

  • FunctionToolCallback#call(String toolInput, @nullable ToolContext toolContext)

    @Override public String call(String toolInput, @Nullable ToolContext toolContext) { Assert.hasText(toolInput, "toolInput cannot be null or empty");

    logger.debug("Starting execution of tool: {}", toolDefinition.name());
    
    I request = JsonParser.fromJson(toolInput, toolInputType);
    O response = toolFunction.apply(request, toolContext);
    
    logger.debug("Successful execution of tool: {}", toolDefinition.name());
    
    return toolCallResultConverter.convert(response, null);
    

    }

你这个是client端吧,@Tools 方法ToolContext 里面就一个exchange的对象

lghgf123 avatar Apr 24 '25 10:04 lghgf123

@heavenwing

Image

Can you provide a minimal example?

我的RequestContextHolder.getRequestAttributes() 直接就是null的,难道SpringBootApplication里面有什么特殊配置吗?

my RequestContextHolder.getRequestAttributes() just return null, I wonder that there is any special configuration in SpringBootApplication?

heavenwing avatar Apr 24 '25 11:04 heavenwing

你这个是client端吧,@tools 方法ToolContext 里面就一个exchange的对象

是的,我这个是clinet,server端我还没试。 这里讨论的是WebFlux 中调用Tool

zz-zhi54 avatar Apr 25 '25 01:04 zz-zhi54

Related to #2378

zz-zhi54 avatar Apr 25 '25 01:04 zz-zhi54

In SyncMcpToolCallback#call(String toolArguments, ToolContext toolContext), the source code has already said

	@Override
	public String call(String toolArguments, ToolContext toolContext) {
		// ToolContext is not supported by the MCP tools
		return this.call(toolArguments);
	}

ToolContext is not supported by the MCP tools

zz-zhi54 avatar Apr 25 '25 01:04 zz-zhi54

我需要在mcp server端来获取headers里的信息

18801151992 avatar Apr 25 '25 06:04 18801151992

已放弃使用Java来开发Remote MCP Server,.NET开发的话,这些问题都不是问题。不过我现在是用Python实现了Local MCP Server,但是使用的时候,还是加载为Semantic Kernel的Function。

I have abandoned using Java to develop the Remote MCP Server. With .NET development, these issues are not a problem. However, I am currently using Python to implement the Local MCP Server, but when using it, it is still loaded as a Semantic Kernel Function.

heavenwing avatar Apr 25 '25 06:04 heavenwing

No matter what language you use to develop MCP, it is the same. Please refer to #2432.

java:Unless you change SyncMcpToolCallback#call(String toolArguments, ToolContext toolContext) or AsyncMcpToolCallback#call(String toolArguments, ToolContext toolContext)

zz-zhi54 avatar Apr 25 '25 06:04 zz-zhi54

No, .NET implementation is working.

Image

sample code is here:

MonkeyMCP-main.zip

heavenwing avatar Apr 25 '25 06:04 heavenwing

Wait for the community to update, or submit a PR. This implementation is very simple, I don't have time recently。

I'm not sure what the official position is. Some of them seem to think that the current framework or even the protocol (SSE/STDIO transport) does not support ToolContext functionality.

zz-zhi54 avatar Apr 25 '25 06:04 zz-zhi54

SSE is a temporary choice, Streamable HTTP will be official HTTP Protocol.

heavenwing avatar Apr 25 '25 06:04 heavenwing

Any updates or solution here?

Both webmvc and webflux have same issue. I tested both on mcp server. Can't access HttpHeader from neither RequestContextHolder and ToolContext in tool (version 1.0.0-M7)

junan-trustarc avatar Apr 25 '25 22:04 junan-trustarc

我使用的M7,AsyncMcpToolCallback 中的call已经明确写了ToolContext is not supported by the MCP tools

	@Override
	public String call(String toolArguments, ToolContext toolContext) {
		// ToolContext is not supported by the MCP tools
		return this.call(toolArguments);
	}

我是需要在Server端校验用户,目前是只能在client端把token跟content一块发送,在mcp server端通过@ToolParam(description = "token") 来获取

{
   “content”:"realContent",
   "token":"xxxx"
}

lyxfn avatar Apr 28 '25 03:04 lyxfn

我使用的M7,AsyncMcpToolCallback 中的call已经明确写了ToolContext is not supported by the MCP tools

	@Override
	public String call(String toolArguments, ToolContext toolContext) {
		// ToolContext is not supported by the MCP tools
		return this.call(toolArguments);
	}

我是需要在Server端校验用户,目前是只能在client端把token跟content一块发送,在mcp server端通过@ToolParam(description = "token") 来获取

{
   “content”:"realContent",
   "token":"xxxx"
}

按理说,@ToolParam,你就需要把token放到模型prompt里面,让模型提取赋值,其实这种不安全,提示词是可以被用户输入篡改的

lwphk avatar Apr 28 '25 06:04 lwphk

我使用的M7,AsyncMcpToolCallback 中的call已经明确写了ToolContext is not supported by the MCP tools

	@Override
	public String call(String toolArguments, ToolContext toolContext) {
		// ToolContext is not supported by the MCP tools
		return this.call(toolArguments);
	}

我是需要在Server端校验用户,目前是只能在client端把token跟content一块发送,在mcp server端通过@ToolParam(description = "token") 来获取

{
   “content”:"realContent",
   "token":"xxxx"
}

按理说,@ToolParam,你就需要把token放到模型prompt里面,让模型提取赋值,其实这种不安全,提示词是可以被用户输入篡改的

是的,这个是有安全风险,不过我目前是demo阶段,还没有找到比较好的处理方案,我看到有很多人提了类似的需求,官方应该会在未来版本有解决方案。

lyxfn avatar Apr 28 '25 08:04 lyxfn