You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Spring AI MCP Server中如何提取请求URL中的sessionId并传递至@Tool注解的工具函数

Spring AI MCP Server中如何提取请求URL中的sessionId并传递至@Tool注解的工具函数

我之前在基于Spring AI开发MCP服务的时候,也踩过异步工具调用丢失请求上下文的坑——用RequestContextHolder拿不到参数,就是因为工具执行在新线程里,上下文没传过去。下面给你几个经过实践验证的解决方案,按推荐程度排序:

方案一:利用Spring AI原生对话上下文(最推荐)

Spring AI本身提供了Conversation(对话)机制,用来维护对话状态,它会自动跟随工具调用流程,不管线程怎么切换都能拿到。

步骤1:在接口层提取sessionId并存入对话上下文

首先在/mcp/message接口里,把请求参数里的sessionId放到Conversation的metadata中:

@PostMapping("/mcp/message")
public ResponseEntity<Message> handleMessage(@RequestParam String sessionId, @RequestBody Message request) {
    // 通过sessionId获取或创建对话(你需要自己实现conversationService)
    Conversation conversation = conversationService.getOrCreateConversation(sessionId);
    // 把sessionId存入对话元数据
    conversation.getMetadata().put("sessionId", sessionId);
    
    // 调用AI生成响应,对话上下文会自动传递给工具
    Message response = aiClient.generate(request, conversation);
    return ResponseEntity.ok(response);
}

步骤2:在@Tool函数中获取sessionId

直接在工具函数的参数里注入Conversation,就能从metadata里取出sessionId:

@Tool
public String userAuthenticationTool(Conversation conversation) {
    String sessionId = (String) conversation.getMetadata().get("sessionId");
    // 这里写你的登录验证逻辑,比如根据sessionId查询用户信息
    return String.format("已验证sessionId: %s,用户身份有效", sessionId);
}

这个方案完全贴合Spring AI的设计,不用自己处理线程上下文传递,可靠性最高。

方案二:自定义线程池传递ThreadLocal上下文

如果你的场景必须用ThreadLocal,那就要确保线程池在执行任务时复制上下文。Spring的ThreadPoolTaskExecutor支持通过TaskDecorator实现这一点。

步骤1:定义Session上下文Holder

public class SessionContextHolder {
    private static final ThreadLocal<String> sessionIdHolder = new ThreadLocal<>();

    public static void setSessionId(String sessionId) {
        sessionIdHolder.set(sessionId);
    }

    public static String getSessionId() {
        return sessionIdHolder.get();
    }

    public static void clear() {
        sessionIdHolder.remove();
    }
}

步骤2:添加拦截器设置sessionId

在请求进入时把sessionId存入ThreadLocal,请求结束后清理:

@Component
public class SessionIdInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String sessionId = request.getParameter("sessionId");
        if (StringUtils.hasText(sessionId)) {
            SessionContextHolder.setSessionId(sessionId);
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        SessionContextHolder.clear();
    }
}

别忘了在Spring配置里注册这个拦截器,让它作用于/mcp/message接口。

步骤3:配置带TaskDecorator的线程池

让Spring AI的异步工具调用使用这个线程池,确保上下文被复制:

@Configuration
public class AsyncToolExecutorConfig {
    @Bean(name = "aiToolExecutor")
    public Executor aiToolExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(20);
        executor.setThreadNamePrefix("AI-Tool-Worker-");
        
        // 关键:用TaskDecorator复制ThreadLocal里的sessionId
        executor.setTaskDecorator(runnable -> {
            String currentSessionId = SessionContextHolder.getSessionId();
            return () -> {
                try {
                    SessionContextHolder.setSessionId(currentSessionId);
                    runnable.run();
                } finally {
                    SessionContextHolder.clear();
                }
            };
        });
        executor.initialize();
        return executor;
    }
}

然后要确保Spring AI的工具调用使用这个线程池,你可以在AiClient的配置里指定executor,或者通过@Async("aiToolExecutor")注解工具方法(如果工具是异步执行的话)。

步骤4:在工具函数中获取sessionId

@Tool
public String userAuthenticationTool() {
    String sessionId = SessionContextHolder.getSessionId();
    // 业务逻辑
    return String.format("通过ThreadLocal拿到sessionId: %s", sessionId);
}

方案三:直接将sessionId作为工具参数传递

这个方案最直接,不需要处理上下文,只要让AI在调用工具时把sessionId作为参数传进去就行。

步骤1:在接口层构建提示词时明确传递sessionId

@PostMapping("/mcp/message")
public ResponseEntity<Message> handleMessage(@RequestParam String sessionId, @RequestBody Message request) {
    // 在提示词里明确告诉AI,调用工具时必须传入sessionId参数
    String prompt = String.format("用户请求:%s,请调用相关工具时,务必将sessionId=%s作为参数传入", 
                                 request.getContent(), sessionId);
    Message aiRequest = Message.from(prompt);
    
    Message response = aiClient.generate(aiRequest);
    return ResponseEntity.ok(response);
}

步骤2:工具函数接收sessionId参数

@Tool
public String userAuthenticationTool(String sessionId) {
    // 直接使用sessionId做验证
    return String.format("工具收到sessionId: %s,验证通过", sessionId);
}

这个方案的缺点是依赖AI对提示词的理解,如果AI没按要求传参数,工具就拿不到值。适合简单场景,或者你能通过prompt engineering确保AI正确传递参数。


内容来源于stack exchange

火山引擎 最新活动