观察Springboot AI-Function Tools 执行过程
观察Springboot AI-Function Tools 执行过程
- Springboot AI-Function Tools 执行流程
- 构造一个Spring AI MCP Server
- 观察Springboot AI应用程序将MCP(Stdio)结合Deepseek的过程
- 单元测试
- 第一次对模型提问
- 第一次模型返回
- 第二次对模型提问
- 第二次模型返回
- 观察Springboot AI应用程序将MCP(sse)结合Deepseek的过程
- 修改并启动MCP服务
- 单元测试
- 第一次对模型提问
- 第一次模型返回
- 第二次对模型提问
- 第二次模型响应
- 调用时序图
- 附录
- POM依赖
Springboot AI-Function Tools 执行流程

构造一个Spring AI MCP Server
一个简易的查询公司员工的MCP Server工程Demo
下载MCP工程后达成JAR,并复制jar的完整路径

观察Springboot AI应用程序将MCP(Stdio)结合Deepseek的过程
单元测试
@Test
public void mcp_model_stdio_call_tool_test(){ServerParameters serverParameters = ServerParameters.builder("java").args("-Dspring.ai.mcp.server.stdio=true", "-jar", "/Users/jiangyangang/gitee_project/ai-mcp-server-demo/target/ai-mcp-server-demo-1.0.0.jar").env(null).build();StdioClientTransport stdioClientTransport = new StdioClientTransport(serverParameters);McpSyncClient mcpSyncClient = McpClient.sync(stdioClientTransport).requestTimeout(Duration.ofSeconds(50L)).build();mcpSyncClient.initialize();OpenAiApi openAiApi = OpenAiApi.builder().baseUrl("https://api.deepseek.com/").completionsPath("v1/chat/completions").embeddingsPath("v1/embeddings").restClientBuilder(RestClient.builder().requestInterceptor(new RestClientFilter())).apiKey("sk-529fb4e1f9e448b6bbe2a252a0f5c796").build();OpenAiChatModel chatModel = OpenAiChatModel.builder().openAiApi(openAiApi).defaultOptions(OpenAiChatOptions.builder().model("deepseek-chat").build()).build();ChatClient chatClient = ChatClient.builder(chatModel).defaultToolCallbacks(new SyncMcpToolCallbackProvider(mcpSyncClient).getToolCallbacks()).defaultAdvisors(new SimpleLoggerAdvisor()).build();String system = "你是一个查询员工信息的小助手,你可以通过我给你的MCP接口【getSalaryDetailInfo】来查询员工的信息,包括地址和职位";String content = chatClient.prompt(new SystemPromptTemplate(system).create()).user("员工蒋颜刚的职位是什么?").call().content();System.out.println(content);
}
第一次对模型提问
提问内容(缩略)
在提问时,spring ai会在promote中加入**tools**字段来告诉模型现在有哪些MCP服务可以调用,
parameters :mcp服务中需要提供哪些参数
name :mcp服务接口名称
description :mcp服务描述
required :哪些字段是必须要传的
{"messages": [{"content": "你是一个查询员工信息的小助手,你可以通过我给你的MCP接口【getSalaryDetailInfo】来查询员工的信息,包括地址和职位","role": "system"},{"content": "员工蒋颜刚的职位是什么?","role": "user"}],"model": "deepseek-chat","stream": false,"tools": [{"type": "function","function": {"description": "获取公司某个员工详细信息的接口","name": "JavaSDKMCPClient_getSalaryDetailInfo","parameters": {"additionalProperties": false,"type": "object","properties": {"request": {"type": "object","properties": {"name": {"type": "string","description": "员工名字"}},"required": ["name"]}},"required": ["request"]}}}]
}
第一次模型返回
tool_calls :AI应用重新应该调用哪些mcp服务
name :mcp服务接口名字
arguments :mcp服务接口字段和对应的参数
tool_call_id : 调用MCP服务的请求流水ID,由大模型生成
{"id": "6e069021-23b8-41d5-a8f7-44d06b20161a","object": "chat.completion","created": 1757908504,"model": "deepseek-chat","choices": [{"index": 0,"message": {"role": "assistant","content": "我来帮您查询员工蒋颜刚的职位信息。","tool_calls": [{"index": 0,"id": "call_00_sVqnvhaz8xn3O8aAq8MM6FsB","type": "function","function": {"name": "JavaSDKMCPClient_getSalaryDetailInfo","arguments": "{\"request\": {\"name\": \"蒋颜刚\"}}"}}]},"logprobs": null,"finish_reason": "tool_calls"}],"usage": {"prompt_tokens": 225,"completion_tokens": 38,"total_tokens": 263,"prompt_tokens_details": {"cached_tokens": 0},"prompt_cache_hit_tokens": 0,"prompt_cache_miss_tokens": 225},"system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
}
第二次对模型提问
在第二次提问时,AI应用程序会新增一个角色tool 用来告诉模型上一笔调用mcp的结果,大模型根据tool_call_id 就能够进行工具的关联。
{"messages": [{"content": "你是一个查询员工信息的小助手,你可以通过我给你的MCP接口【getSalaryDetailInfo】来查询员工的信息,包括地址和职位","role": "system"},{"content": "员工蒋颜刚的职位是什么?","role": "user"},{"content": "我来帮您查询员工蒋颜刚的职位信息。","role": "assistant","tool_calls": [{"id": "call_00_sVqnvhaz8xn3O8aAq8MM6FsB","type": "function","function": {"name": "JavaSDKMCPClient_getSalaryDetailInfo","arguments": "{\"request\": {\"name\": \"蒋颜刚\"}}"}}]},{"content": "[{\"text\":\"{\\\"address\\\":\\\"深圳\\\",\\\"job\\\":\\\"后端开发工程师\\\"}\"}]","role": "tool","name": "JavaSDKMCPClient_getSalaryDetailInfo","tool_call_id": "call_00_sVqnvhaz8xn3O8aAq8MM6FsB"}],"model": "deepseek-chat","stream": false,"tools": [....]
}
第二次模型返回
{"id": "9e376a0e-8cb7-41f2-be98-b973996d418f","object": "chat.completion","created": 1757908508,"model": "deepseek-chat","choices": [{"index": 0,"message": {"role": "assistant","content": "根据查询结果,员工蒋颜刚的职位是**后端开发工程师**,工作地点在深圳。"},"logprobs": null,"finish_reason": "stop"}],"usage": {"prompt_tokens": 287,"completion_tokens": 22,"total_tokens": 309,"prompt_tokens_details": {"cached_tokens": 256},"prompt_cache_hit_tokens": 256,"prompt_cache_miss_tokens": 31},"system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
}
观察Springboot AI应用程序将MCP(sse)结合Deepseek的过程
修改并启动MCP服务
在ai-mcp-server-employee服务中加入webflux依赖
<!-- sse 模式需要配置 webflux -->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
</dependency>
之后启动MCP服务
单元测试
@Test
public void mcp_model_sse_call_tool_test(){HttpClientSseClientTransport sseClientTransport = HttpClientSseClientTransport.builder("http://127.0.0.1:9102/").sseEndpoint("sse").build();McpSyncClient mcpSyncClient = McpClient.sync(sseClientTransport).requestTimeout(Duration.ofSeconds(5L)).build();mcpSyncClient.initialize();
//McpSchema.ListToolsResult listToolsResult = mcpSyncClient.listTools(null);System.out.println(JSONObject.toJSONString(listToolsResult));OpenAiApi openAiApi = OpenAiApi.builder().baseUrl("https://api.deepseek.com/").completionsPath("v1/chat/completions").embeddingsPath("v1/embeddings").restClientBuilder(RestClient.builder().requestInterceptor(new RestClientFilter())).apiKey("sk-xxxx").build();OpenAiChatModel chatModel = OpenAiChatModel.builder().openAiApi(openAiApi).defaultOptions(OpenAiChatOptions.builder().model("deepseek-chat").build()).build();ChatClient chatClient = ChatClient.builder(chatModel).defaultToolCallbacks(new SyncMcpToolCallbackProvider(mcpSyncClient).getToolCallbacks()).defaultAdvisors(new SimpleLoggerAdvisor()).build();String system = "你是一个查询员工信息的小助手,你可以通过我给你的MCP接口【getSalaryDetailInfo】来查询员工的信息,包括地址和职位";String content = chatClient.prompt(new SystemPromptTemplate(system).create()).user("员工蒋颜刚的职位是什么?").call().content();System.out.println(content);
}
第一次对模型提问
提问内容(缩略)
在提问时,spring ai会在promote中加入**tools**字段来告诉模型现在有哪些MCP服务可以调用,
parameters :mcp服务中需要提供哪些参数
name :mcp服务接口名称
description :mcp服务描述
required :哪些字段是必须要传的
{"messages": [{"content": "你是一个查询员工信息的小助手,你可以通过我给你的MCP接口【getSalaryDetailInfo】来查询员工的信息,包括地址和职位","role": "system"},{"content": "员工蒋颜刚的职位是什么?","role": "user"}],"model": "deepseek-chat","stream": false,**"tools": [{"type": "function","function": {"description": "获取公司某个员工详细信息的接口","name": "JavaSDKMCPClient_getSalaryDetailInfo","parameters": {"additionalProperties": false,"type": "object","properties": {"request": {"type": "object","properties": {"name": {"type": "string","description": "员工名字"}},"required": ["name"]}},"required": ["request"]}}}]**
}
第一次模型返回
tool_calls :AI应用重新应该调用哪些mcp服务
name :mcp服务接口名字
arguments :mcp服务接口字段和对应的参数
tool_call_id : 调用MCP服务的请求流水ID,由大模型生成
{"id": "475fe411-4ac4-44d3-bc09-71a3ac895a73","object": "chat.completion","created": 1757683715,"model": "deepseek-chat","choices": [{"index": 0,"message": {"role": "assistant","content": "我来帮您查询蒋颜刚的职位信息。",**"tool_calls": [{"index": 0,"id": "call_00_5JnhTbbmeICprFEMbzgnNkxJ","type": "function","function": {"name": "JavaSDKMCPClient_getSalaryDetailInfo","arguments": "{\"request\": {\"name\": \"蒋颜刚\"}}"}}]**},"logprobs": null,"finish_reason": "tool_calls"}],"usage": {"prompt_tokens": 225,"completion_tokens": 37,"total_tokens": 262,"prompt_tokens_details": {"cached_tokens": 192},"prompt_cache_hit_tokens": 192,"prompt_cache_miss_tokens": 33},"system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
}
第二次对模型提问
在第二次提问时,AI应用程序会新增一个角色**tool** 用来告诉模型上一笔调用mcp的结果,大模型根据**tool_call_id** 就能够进行工具的关联。
{"messages": [{"content": "你是一个查询员工信息的小助手,你可以通过我给你的MCP接口【getSalaryDetailInfo】来查询员工的信息,包括地址和职位","role": "system"},{"content": "员工蒋颜刚的职位是什么?","role": "user"},{"content": "我来帮您查询蒋颜刚的职位信息。","role": "assistant","tool_calls": [{"id": "call_00_frX3BDPuCRge9oBnwKWgBiCY","type": "function","function": {"name": "JavaSDKMCPClient_getSalaryDetailInfo","arguments": "{\"request\": {\"name\": \"蒋颜刚\"}}"}}]},{"content": "[{\"text\":\"{\\\"address\\\":\\\"深圳\\\",\\\"job\\\":\\\"后端开发工程师\\\"}\"}]","role": "tool","name": "JavaSDKMCPClient_getSalaryDetailInfo","tool_call_id": "call_00_frX3BDPuCRge9oBnwKWgBiCY"}],"model": "deepseek-chat","stream": false,"tools": [{"type": "function","function": {"description": "获取公司某个员工详细信息的接口","name": "JavaSDKMCPClient_getSalaryDetailInfo","parameters": {"additionalProperties": false,"type": "object","properties": {"request": {"type": "object","properties": {"name": {"type": "string","description": "员工名字"}},"required": ["name"]}},"required": ["request"]}}}]
}
第二次模型响应
{"id": "f7d8c859-1858-4421-9ea3-361e754ed4ee","object": "chat.completion","created": 1757684702,"model": "deepseek-chat","choices": [{"index": 0,"message": {"role": "assistant","content": "根据查询结果,员工蒋颜刚的职位是**后端开发工程师**,工作地点在深圳。"},"logprobs": null,"finish_reason": "stop"}],"usage": {"prompt_tokens": 286,"completion_tokens": 22,"total_tokens": 308,"prompt_tokens_details": {"cached_tokens": 256},"prompt_cache_hit_tokens": 256,"prompt_cache_miss_tokens": 30},"system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
}
调用时序图

附录
POM依赖
<!-- spring ai 1.0.0 https://central.sonatype.com/artifact/org.springframework.ai/spring-ai-bom -->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>1.0.0</version><type>pom</type><scope>import</scope>
</dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-vector-store-pgvector</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client-webflux</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-tika-document-reader</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.28</version>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version>
</dependency>
