在 Quarkus 中实现 MCP 服务器

模型上下文协议 (MCP) 是一项新兴标准,可让 AI 模型安全地与外部工具和资源进行交互。在本教程中,我将向您展示如何使用 Quarkus 实现 MCP 服务器,从而允许您使用由 Java 生态系统驱动的自定义工具来扩展 AI 应用程序。

我们将要构建的内容

我们将实现一个简单的 MCP 服务器,该服务器提供用于获取美国地区天气预报和警报的工具。我们选择此示例是因为它与官方 MCP 快速入门指南 modelcontextprotocol.io/quickstart/server 一致,从而更容易比较不同语言的实现。

我们的服务器将公开两个工具:getAlertsgetForecast。构建完成后,我们将将其连接到 MCP 主机,该主机将服务器作为子进程运行。集成 Claude 后的效果如下:

Claude MCP Integration Example

核心 MCP 概念

MCP 服务器可以提供三种主要功能:

资源

客户端可以读取的类文件数据(如 API 响应或文件内容)

工具

可由 LLM 调用(经用户批准)的函数

提示

预先编写的模板,帮助用户完成特定任务

本教程重点介绍工具的实现。

先决条件

要遵循本教程,您需要

  • 熟悉 Quarkus 和 Java

  • 了解 LLM(OpenAI、Granite、Anthropic、Google 等)

系统要求

  • Quarkus CLI

  • JBang(可选)

设置您的项目

首先,使用 rest-client、qute 和 mcp 服务器扩展创建一个新的 Quarkus 项目,但不包含默认的样板代码。

quarkus create app --no-code -x rest-client-jackson,qute,mcp-server-stdio weather

我们使用的是 stdio 变体,因为 MCP 主机将服务器作为子进程运行时需要它。虽然存在用于服务器发送事件流的 sse 变体,但我们将重点关注标准输入/输出方法。

构建服务器

创建一个新文件 src/main/java/org/acme/Weather.java。此示例的完整代码可在 此处 找到。

天气 API 集成

首先,我们为天气 API 设置 REST 客户端。

@RegisterRestClient(baseUri = "https://api.weather.gov")
public interface WeatherClient {
    // Get active alerts for a specific state
    @GET
    @Path("/alerts/active/area/{state}")
    Alerts getAlerts(@RestPath String state);

    // Get point metadata for coordinates
    @GET
    @Path("/points/{latitude},{longitude}")
    JsonObject getPoints(@RestPath double latitude, @RestPath double longitude);

    // Get detailed forecast using dynamically provided URL
    @GET
    @Path("/")
    Forecast getForecast(@Url String url);
}

为了处理 API 响应,我们将定义一些数据类。请注意,我们仅包含所需的字段,因为完整的 API 响应包含更多数据。

static record Period(
    String name,
    int temperature,
    String temperatureUnit,
    String windSpeed,
    String windDirection,
    String detailedForecast) {
}

static record ForecastProperties(
        List<Period> periods) {
}

static record Forecast(
        ForecastProperties properties) {
}

由于天气 API 使用重定向,请将此添加到您的 application.properties 文件中。

quarkus.rest-client.follow-redirects=true

格式化助手

我们将使用 Qute 模板来格式化天气数据。

String formatForecast(Forecast forecast) {
    return forecast.properties().periods().stream().map(period -> {
            // Template for each forecast period
            return Qute.fmt(
                """
                        Temperature: {p.temperature}°{p.temperatureUnit}
                        Wind: {p.windSpeed} {p.windDirection}
                        Forecast: {p.detailedForecast}
                        """,
                Map.of("p", period)).toString();
        }).collect(Collectors.joining("\n---\n"));
    }

实现 MCP 工具

现在,让我们实现实际的 MCP 工具。来自 io.quarkiverse.mcp.server@Tool 注解将方法标记为可用工具,而 @ToolArg 则描述了参数。

@Tool(description = "Get weather alerts for a US state.")
String getAlerts(@ToolArg(description = "Two-letter US state code (e.g. CA, NY)") String state) {
    return formatAlerts(weatherClient.getAlerts(state));
}

@Tool(description = "Get weather forecast for a location.")
String getForecast(
    @ToolArg(description = "Latitude of the location") double latitude,
    @ToolArg(description = "Longitude of the location") double longitude) {

    // First get the point metadata which contains the forecast URL
    var points = weatherClient.getPoints(latitude, longitude);
    // Extract the forecast URL using Qute template
    var url = Qute.fmt("{p.properties.forecast}", Map.of("p", points));
    // Get and format the forecast
    return formatForecast(weatherClient.getForecast(url));
}

预报 API 需要一个两步过程,我们首先获取点元数据,然后使用该响应中的 URL 来获取实际预报。

运行服务器

为了简化部署和开发,我们将服务器打包为 uber-jar。这使得可以 mvn install 并将其作为 jar 发布到 Maven 存储库,从而更轻松地与他人共享和运行。

quarkus.package.uber-jar=true

最后,我们可以选择启用文件日志记录,因为如果没有文件日志记录,我们将无法看到服务器的任何日志,因为标准输入/输出是为 MCP 协议保留的。

quarkus.log.file.enable=true
quarkus.log.file.path=weather-quarkus.log

运行 mvn install 后,您可以使用 JBang 使用其 Maven 坐标运行服务器:org.acme:weather:1.0.0-SNAPSHOT:runner,或手动使用 java -jar target/weather-1.0.0-SNAPSHOT-runner.jar 运行。

与 Claude Desktop 集成

将此添加到您的 claude_desktop_config.json 文件中。

{
    "mcpServers": {
        "weather": {
            "command": "jbang",
            "args": ["--quiet",
                    "org.acme:weather:1.0.0-SNAPSHOT:runner"]
        }
    }
}

--quiet 标志可防止 JBang 的输出干扰 MCP 协议。

Claude Tools Integration

您也可以直接运行服务器而不使用 java,那么它将是 java -jar <FULL PATH>/weather-1.0.0-SNAPSHOT-runner.jar。我们在这里使用 JBang 是因为它对于想与不想本地构建 MCP 服务器的人共享更简单。

开发工具

MCP Inspector

为了进行开发和测试,您可以使用 MCP Inspector 工具。

npx @modelcontextprotocol/inspector

这会启动一个本地 Web 服务器,您可以在其中测试您的 MCP 服务器。

MCP Inspector Interface

与 LangChain4j 集成

从 0.23.0 版本开始,Quarkus LangChain4j 支持 MCP,这意味着它充当 MCP 客户端。有关详细信息,请参阅 将模型上下文协议与 Quarkus+LangChain4j 结合使用

要将我们的天气服务器与 LangChain4j 结合使用,请添加此配置。

quarkus.langchain4j.mcp.weather.transport-type=stdio
quarkus.langchain4j.mcp.weather.command=jbang,--quiet,org.acme:weather:1.0.0-SNAPSHOT:runner

其他客户端/MCP 主机

模型上下文协议有一个列出已知客户端的页面。

虽然我没有测试所有各种客户端和 MCP 主机,但使用 jbang --quiet <GAV> 的类似方法应该适用于大多数(如果不是全部)客户端。

测试服务器

您可以通过 Claude 或其他 MCP 主机测试服务器,查询方式如下:

  • “索尔万格的天气预报是什么?”

  • “纽约市的天气警报是什么?”

幕后发生的事情如下:

  1. 您的问题以及可用的工具信息会发送给 LLM。

  2. LLM 分析问题并确定要使用哪些工具。

  3. 客户端通过 MCP 服务器执行选定的工具。

  4. 结果返回给 LLM。

  5. LLM 使用工具结果构建答案。

  6. 您看到最终的响应!

结论

我们已经看到 Quarkus 如何使实现 MCP 服务器变得简单,与实现相比,只需要最少的样板代码。Quarkus 的扩展系统和 JBang 的结合使得开发和部署非常愉快。