基于 HTTP 构建 MCP Tools 的完整工程解析

基于 HTTP 构建 MCP Tools 的完整工程解析

一、项目目标

本文基于以下代码实现,完整讲解:

  • 如何将 HTTP API 封装为 AI Tool
  • 如何构建 MCP(Model Context Protocol)风格的 Tool 执行体系
  • 如何实现多轮 Tool 调用(Agent Loop)
  • 如何进行工程化扩展

二、整体架构

复制代码
User -> LLM -> ToolSpecification -> ToolCall -> HttpToolExecutor -> HTTP API -> Result -> LLM

核心流程:

  1. 用户输入问题
  2. LLM 判断是否需要调用 Tool
  3. 生成 ToolExecutionRequest
  4. 执行 HTTP 请求
  5. 返回结果给 LLM
  6. 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(核心执行引擎)

这是整个系统最核心部分。

执行流程:
  1. 发送请求给 LLM
  2. 流式接收输出
  3. 检测 Tool 调用
  4. 执行 Tool
  5. 回写结果
  6. 递归下一轮

关键代码
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)

流程:

  1. 查找 Tool
  2. 解析参数
  3. 执行 HTTP
  4. 返回结果

(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 平台
相关推荐
i建模2 小时前
SSL: CERTIFICATE_VERIFY_FAILED feishu 机器人CoPaw
运维·网络·网络协议·ssl·openclaw
艾莉丝努力练剑2 小时前
alarm系统调用的一次性原理揭秘
linux·运维·服务器·开发语言·网络·人工智能·学习
王码码20352 小时前
Flutter for OpenHarmony:使用 pluto_grid 打造高性能数据网格
flutter·http·华为·架构·harmonyos
先跑起来再说2 小时前
从原理到实践:彻底搞懂Cookie和Session的区别
计算机网络·http·https
兰.lan2 小时前
【黑马ai测试】HTTP协议-抓包工具定位-弱网测试-缺陷介绍
网络·python·网络协议·http
ACP广源盛139246256732 小时前
IX8024@ACP#重构新一代 AI 算力产品的高速扩展架构
网络·人工智能·嵌入式硬件·计算机外设·电脑
GISer_Jing2 小时前
Claude Code架构深度解析:从核心文件到Harness的确定性控制体系
ai·架构·aigc
AI自动化工坊2 小时前
AI Agent框架深度解析:Superpowers与gstack如何重构开发工作流?
人工智能·ai·重构·开源
王小义笔记3 小时前
解决使用WSL客户端养龙虾后C盘空间告急的问题
ubuntu·ai·键盘·openclaw