基于 HTTP 构建 MCP Tools 的完整工程解析
一、项目目标
本文基于以下代码实现,完整讲解:
- 如何将 HTTP API 封装为 AI Tool
- 如何构建 MCP(Model Context Protocol)风格的 Tool 执行体系
- 如何实现多轮 Tool 调用(Agent Loop)
- 如何进行工程化扩展
二、整体架构
User -> LLM -> ToolSpecification -> ToolCall -> HttpToolExecutor -> HTTP API -> Result -> LLM
核心流程:
- 用户输入问题
- LLM 判断是否需要调用 Tool
- 生成 ToolExecutionRequest
- 执行 HTTP 请求
- 返回结果给 LLM
- LLM 继续推理或输出最终结果
三、核心代码解析
1. 主程序 StreamApp2
作用:驱动整个 Tool 调用流程
关键逻辑:
(1)加载 Tool 配置
java
package com.nbsaas.boot.tools;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nbsaas.boot.AiConfig;
import com.nbsaas.boot.tools.http.HttpToolDefinition;
import com.nbsaas.boot.tools.http.HttpToolExecutor;
import dev.langchain4j.agent.tool.ToolExecutionRequest;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.data.message.*;
import dev.langchain4j.model.chat.request.ChatRequest;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class StreamApp2 {
private static final ObjectMapper objectMapper = new ObjectMapper();
public static void main(String[] args) throws Exception {
// 从 classpath 加载 tools.json
List<HttpToolDefinition> toolDefs = loadToolDefinitions("tools.json");
HttpToolExecutor httpExecutor = new HttpToolExecutor(toolDefs);
OpenAiStreamingChatModel model = AiConfig.getOpenAiStreamingChatModel();
List<ToolSpecification> toolSpecs = httpExecutor.buildToolSpecifications();
List<ChatMessage> messages = new ArrayList<>();
messages.add(UserMessage.from("北京今天天气怎么样,适合出行吗"));
chatLoop(model, toolSpecs, httpExecutor, messages, 1, 5);
}
// ========== 核心循环 ==========
private static void chatLoop(
OpenAiStreamingChatModel model,
List<ToolSpecification> toolSpecs,
HttpToolExecutor httpExecutor,
List<ChatMessage> messages,
int round,
int maxRounds) throws InterruptedException {
if (round > maxRounds) {
System.err.println("[警告] 已达到最大工具调用轮次 " + maxRounds + ",强制终止");
return;
}
System.out.println("\n===== 第 " + round + " 轮请求 =====");
CountDownLatch latch = new CountDownLatch(1);
ChatRequest request = ChatRequest.builder()
.messages(messages)
.toolSpecifications(toolSpecs)
.build();
// 用数组包装,让 lambda 内可修改
final boolean[] hasToolCall = {false};
model.chat(request, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partial) {
System.out.print(partial);
}
@Override
public void onCompleteResponse(ChatResponse response) {
AiMessage aiMessage = response.aiMessage();
// 1. 把 AI 回复加入历史
messages.add(aiMessage);
// 2. 检查是否有工具调用
if (aiMessage.hasToolExecutionRequests()) {
hasToolCall[0] = true;
System.out.println("\n[检测到工具调用,开始执行...]");
for (ToolExecutionRequest req : aiMessage.toolExecutionRequests()) {
System.out.printf(" → Tool: %-20s 参数: %s%n",
req.name(), req.arguments());
// 3. 执行 HTTP Tool
String result = httpExecutor.execute(req);
System.out.printf(" ← 结果: %s%n", result);
// 4. 把结果加入历史
messages.add(ToolExecutionResultMessage.from(req, result));
}
} else {
System.out.println("\n[最终回答完成]");
}
latch.countDown();
}
@Override
public void onError(Throwable error) {
System.err.println("\n[错误] " + error.getMessage());
error.printStackTrace();
latch.countDown();
}
});
latch.await();
// 5. 有工具调用结果则继续下一轮
if (hasToolCall[0]) {
chatLoop(model, toolSpecs, httpExecutor, messages, round + 1, maxRounds);
}
}
// ========== 加载 tools.json ==========
private static List<HttpToolDefinition> loadToolDefinitions(String filename) throws Exception {
InputStream is = StreamApp2.class.getClassLoader().getResourceAsStream(filename);
if (is == null) {
throw new IllegalArgumentException("找不到配置文件: " + filename);
}
List<HttpToolDefinition> defs = objectMapper.readValue(
is, new TypeReference<List<HttpToolDefinition>>() {});
System.out.println("[初始化] 加载了 " + defs.size() + " 个 HTTP Tool:");
defs.forEach(d -> System.out.println(" - " + d.getName() + " → " + d.getUrl()));
return defs;
}
}
说明:
- 从 classpath 加载 tools.json
- 支持动态扩展 Tool
(2)构建 Tool 执行器
java
package com.nbsaas.boot.tools.http;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.langchain4j.agent.tool.ToolExecutionRequest;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import okhttp3.*;
public class HttpToolExecutor {
private final Map<String, HttpToolDefinition> definitions;
private final OkHttpClient httpClient;
private final ObjectMapper objectMapper;
public HttpToolExecutor(List<HttpToolDefinition> definitions) {
this.definitions = definitions.stream()
.collect(Collectors.toMap(HttpToolDefinition::getName, d -> d));
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.build();
this.objectMapper = new ObjectMapper();
}
// ========== 对外执行入口 ==========
public String execute(ToolExecutionRequest req) {
HttpToolDefinition def = definitions.get(req.name());
if (def == null) {
return "错误:未找到 HTTP Tool [" + req.name() + "]";
}
try {
Map<String, Object> arguments = parseArguments(req.arguments());
return "POST".equalsIgnoreCase(def.getMethod())
? doPost(def, arguments)
: doGet(def, arguments);
} catch (Exception e) {
return "HTTP Tool 执行失败 [" + req.name() + "]:" + e.getMessage();
}
}
// ========== POST 请求 ==========
private String doPost(HttpToolDefinition def, Map<String, Object> arguments) throws Exception {
String bodyStr = objectMapper.writeValueAsString(arguments);
RequestBody requestBody = RequestBody.create(
bodyStr.getBytes(StandardCharsets.UTF_8),
MediaType.get("application/json")
);
Request request = new Request.Builder()
.url(def.getUrl())
.post(requestBody)
.build();
return executeRequest(request);
}
// ========== GET 请求 ==========
private String doGet(HttpToolDefinition def, Map<String, Object> arguments) throws Exception {
HttpUrl baseUrl = HttpUrl.parse(def.getUrl());
if (baseUrl == null) {
throw new IllegalArgumentException("无效的 URL: " + def.getUrl());
}
HttpUrl.Builder urlBuilder = baseUrl.newBuilder();
arguments.forEach((k, v) -> urlBuilder.addQueryParameter(k, String.valueOf(v)));
Request request = new Request.Builder()
.url(urlBuilder.build())
.get()
.build();
return executeRequest(request);
}
// ========== 公共请求执行 ==========
private String executeRequest(Request request) throws IOException {
try (Response response = httpClient.newCall(request).execute()) {
ResponseBody body = response.body();
String bodyStr = body != null ? body.string() : "";
if (!response.isSuccessful()) {
throw new IOException("HTTP " + response.code() + ": " + bodyStr);
}
return bodyStr.isEmpty() ? "空响应" : bodyStr;
}
}
// ========== 参数解析 ==========
private Map<String, Object> parseArguments(String arguments) throws Exception {
if (arguments == null || arguments.isBlank()) {
return Map.of();
}
return objectMapper.readValue(arguments, new TypeReference<>() {});
}
// ========== 动态构建 ToolSpecification ==========
public List<ToolSpecification> buildToolSpecifications() {
return definitions.values().stream()
.map(this::buildSpecification)
.collect(Collectors.toList());
}
private ToolSpecification buildSpecification(HttpToolDefinition def) {
JsonObjectSchema.Builder schemaBuilder = JsonObjectSchema.builder();
List<String> requiredParams = new ArrayList<>();
if (def.getParams() != null) {
for (HttpToolParam param : def.getParams()) {
switch (param.getType().toLowerCase()) {
case "integer", "int", "long" ->
schemaBuilder.addIntegerProperty(param.getName(), param.getDescription());
case "boolean", "bool" ->
schemaBuilder.addBooleanProperty(param.getName(), param.getDescription());
default ->
schemaBuilder.addStringProperty(param.getName(), param.getDescription());
}
if (param.isRequired()) {
requiredParams.add(param.getName());
}
}
}
return ToolSpecification.builder()
.name(def.getName())
.description(def.getDescription())
.parameters(schemaBuilder.required(requiredParams).build())
.build();
}
}
HttpToolExecutor httpExecutor = new HttpToolExecutor(toolDefs);
说明:
- 统一执行所有 HTTP Tool
- 类似 RPC 调度中心
(3)构建 Tool Specification
java
List<ToolSpecification> toolSpecs = httpExecutor.buildToolSpecifications();
说明:
- 将 Tool 转换为 LLM 可识别结构(JSON Schema)
(4)初始化对话
java
messages.add(UserMessage.from("北京今天天气怎么样,适合出行吗"));
(5)进入 Agent Loop
java
chatLoop(model, toolSpecs, httpExecutor, messages, 1, 5);
2. chatLoop(核心执行引擎)
这是整个系统最核心部分。
执行流程:
- 发送请求给 LLM
- 流式接收输出
- 检测 Tool 调用
- 执行 Tool
- 回写结果
- 递归下一轮
关键代码
java
if (aiMessage.hasToolExecutionRequests()) {
说明:
- 判断 LLM 是否触发 Tool
java
String result = httpExecutor.execute(req);
说明:
- 执行 HTTP 请求
java
messages.add(ToolExecutionResultMessage.from(req, result));
说明:
- 将 Tool 结果返回给 LLM
多轮调用机制
java
chatLoop(... round + 1 ...)
说明:
- 实现 Agent 推理能力
- 支持复杂任务链
3. HttpToolExecutor
作用:HTTP Tool 执行引擎
(1)Tool 注册
java
this.definitions = definitions.stream()
.collect(Collectors.toMap(HttpToolDefinition::getName, d -> d));
说明:
- 构建 Tool 索引
(2)统一执行入口
java
public String execute(ToolExecutionRequest req)
流程:
- 查找 Tool
- 解析参数
- 执行 HTTP
- 返回结果
(3)参数解析
java
objectMapper.readValue(arguments, new TypeReference<>() {});
说明:
- JSON → Map
(4)GET 请求
java
urlBuilder.addQueryParameter(k, String.valueOf(v));
(5)POST 请求
java
RequestBody.create(json)
(6)执行请求
java
httpClient.newCall(request).execute();
说明:
- 使用 OkHttp
4. ToolSpecification 构建
java
schemaBuilder.addStringProperty(...)
说明:
- 动态生成 JSON Schema
支持类型:
- string
- integer
- boolean
5. HttpToolDefinition
java
private String name;
private String description;
private String url;
private String method;
private List<HttpToolParam> params;
说明:
- Tool 元数据定义
6. HttpToolParam
java
private String name;
private String type;
private boolean required;
说明:
- 参数描述
- 用于构建 Schema
四、执行流程(完整链路)
Step 1:用户输入
北京天气怎么样
Step 2:LLM 判断
生成:
ToolExecutionRequest
Step 3:执行 Tool
HttpToolExecutor.execute()
Step 4:调用 HTTP API
GET /weather?city=北京
Step 5:返回结果
{ "temp": 20 }
Step 6:继续推理
LLM 输出最终结果
五、工程价值
优势
- Tool 完全解耦
- 支持动态扩展
- 兼容 MCP
- 支持 Agent
六、优化建议
1. Tool 网关
增加:
- 限流
- 鉴权
- 熔断
2. 参数校验
引入:
- JSON Schema Validator
3. 统一返回结构
json
{
"code": 0,
"data": {}
}
4. 缓存机制
适用于:
- 天气
- 配置接口
5. Tool 平台化
升级为:
- 数据库存储
- UI 管理
七、总结
本项目本质是:
一个轻量级 MCP Tool 执行引擎
具备:
- Tool Schema 构建
- Tool 执行
- 多轮推理
可扩展为:
- AI Gateway
- Agent 平台