Ohhnews

分类导航

$ cd ..
Baeldung原文

Spring AI中的MCP日志记录

#mcp#spring ai#日志记录#java#人工智能

1. 概述

MCP(Model Context Protocol,模型上下文协议)是一种基于 JSON-RPC(JavaScript Object Notation-Remote Procedure Call,JavaScript 对象表示法-远程过程调用)的开源协议。它为 AI(人工智能)应用提供了一种标准化方式,用于连接外部工具、服务和数据源。因此,它消除了自定义集成的需求。

MCP 中的日志记录为服务器提供了一种标准化方式,将带有严重级别标签的结构化日志消息发送给客户端。这在调试 MCP 服务器时可能尤其有用。

在本教程中,我们将讨论 Spring AI 中的 MCP 日志记录。首先,我们会简要介绍 MCP 日志的基本信息;然后,我们会分别在 MCP 服务器和 MCP 客户端中讨论日志记录。

2. MCP 日志记录的基本信息

MCP 服务器会将日志消息作为 notifications/message JSON-RPC 通知推送到 MCP 客户端。这种从服务器到客户端的单向通知消息包含严重级别、可选的记录器名称和日志消息。

MCP 客户端可以向服务器发送 logging/setLevel JSON-RPC 请求,以配置日志消息的详细程度。因此,服务器只会发送达到或超过所请求严重级别的日志消息。客户端也可以通过再次发出 logging/setLevel 来动态调整日志详细级别。

一个重要的注意事项是:2026 年 7 月的规范修订版(2026-07-28) 已正式弃用日志记录功能以及其他一些功能,转而推荐使用更新的机制。不过,现有实现至少还能继续使用一年。建议较新的实现迁移到:对于 stdio(标准输入/输出)传输使用 stderr(标准错误),并使用 OpenTelemetry 进行结构化可观测性。

3. Maven 依赖

让我们先在 pom.xml 中添加必要的 Maven 依赖:

$ xml
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>

MCP 客户端 Starter 依赖使 Spring AI 应用能够连接 MCP 服务器并使用它们的工具。另一方面,我们使用 MCP 服务器 Starter 依赖将一个 Spring Boot 应用变成 MCP 服务器。该依赖让应用可以向其他 LLM(Large Language Model,大语言模型)应用暴露工具和资源。

我们使用 Spring AI BOM(Bill of Materials,物料清单)来避免 Spring AI 依赖之间的版本冲突风险:

$ xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

因此,我们不必显式指定 MCP Starter 的版本。

4. 服务器端日志记录

让我们从 MCP 服务器中的日志记录开始讨论。我们将要讨论的服务器会暴露一个工具,只要用户请求评估密码强度,MCP 客户端就可以调用它。

4.1. 工具实现

PasswordStrengthService 类保存了实际的工具实现,即通过 @McpTool 服务器端注解暴露的 checkStrength() 方法。由于它使用了 @Service 注解,因此是一个 Spring Bean:

$ java
@Service
public class PasswordStrengthService {
    ...
    @McpTool(name = "check_password_strength", 
      description = "Evaluates password strength and returns a score with recommendations.")
    public PasswordStrengthResult checkStrength(
        @McpToolParam(description = "The password to evaluate", required = true) String password,
	  McpSyncRequestContext ctx) {
        ...
    }
}

checkStrength() 方法(也就是该工具)接收两个参数。第一个参数是要评估的密码。工具会检查密码长度是否至少为 12 个字符、是否包含至少一个大写字母,以及是否包含至少一个数字。它还会检查密码是否与诸如 "123456""qwerty" 这样的常见密码匹配。

第二个参数的类型是 McpSyncRequestContext,我们将使用它进行日志记录。Spring AI MCP 注解框架会自动注入该参数。MCP 服务器使用这种特殊类型的对象来记录日志消息。它不会将日志消息直接写入 stdout(标准输出)流,而是将日志消息打包成标准的 MCP 协议通知载荷,并通过传输层(如 stdio 或 SSE(Server-Sent Events,服务器发送事件))发送给客户端。在我们的示例中,我们使用 stdio 传输。

McpSyncRequestContext 接口提供了多种日志方法,例如 debug()info()warn()error()。例如,如果密码与常见密码匹配,我们在示例中通过调用 ctx.error("Password found in common-password list") 来记录这一情况:

$ java
if (COMMON_PASSWORDS.contains(password.toLowerCase())) {
    ctx.error("Password found in common-password list");
    issues.add("commonly used");
}

因此,在服务器端,我们可以针对每次调用选择合适的日志级别

4.2. MCP 服务器

McpLoggingServerApplication 类提供了 MCP 服务器进程的 Spring Boot 入口点

$ java
@SpringBootApplication
public class McpLoggingServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(McpLoggingServerApplication.class, args);
    }
}

当服务器进程启动时,Spring AI 的 MCP 服务器自动配置会扫描带有 @McpTool 注解的方法,然后注册所发现的工具,并通过 stdio 传输暴露给客户端。已连接的客户端因此可以发现并调用这些工具。

我们需要在服务器配置文件 application-server.properties 中启用 stdio 传输:

$ properties
spring.ai.mcp.server.stdio=true

此外,我们必须防止服务器应用向 stdout 打印日志。stdout 流专用于交换 MCP 协议的 JSON-RPC 消息

$ properties
spring.main.web-application-type=none
spring.main.banner-mode=off
logging.pattern.console=

spring.main.web-application-type 设置为 none 可以防止 Spring Boot 启动 Web 服务器,这意味着不会产生任何与 Web 相关的启动日志。将 spring.main.banner-mode 设置为 off 可以禁止 Spring Boot 在启动时打印通常输出到 stdout 的横幅。最后,logging.pattern.console= 会移除控制台日志中的模式格式化。

5. 客户端日志处理

现在我们来讨论 MCP 客户端中的日志记录。

5.1. MCP 客户端

McpLoggingServerApplication 类似,McpLoggingClientApplication 类为 MCP 客户端进程提供了 Spring Boot 入口点

$ java
@SpringBootApplication
public class McpLoggingClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(McpLoggingClientApplication.class, args);
    }
}

启动时,客户端 Boot Starter 会读取 application-client.properties 文件中的 spring.ai.mcp.client.stdio.connections.* 属性,并将服务器作为子进程启动。例如,配置属性 spring.ai.mcp.client.stdio.connections.password-strength-logging-server.args 指定了客户端在启动服务器时要传递给服务器的参数。这些配置属性中的 stdio 部分为指定连接 password-strength-logging-server 选择了 stdio 传输方式

spring.ai.mcp.client.type=SYNC 配置属性用于选择同步客户端实现,即 McpSyncClient。因此,客户端调用会阻塞,直到服务器响应。

5.2. 日志处理程序

为了在客户端捕获服务器发送的通知日志,我们必须使用 @McpLogging 客户端注解:

$ java
@Component public class PasswordStrengthMcpClientHandlers {
    ...
    @McpLogging(clients = "password-strength-logging-server")
    public void handleLoggingMessage(McpSchema.LoggingMessageNotification notification) {
        LOGGER.info("Received server logging notification [{}]: {}"
          , notification.level(), notification.data());
        receivedLogs.add(notification);
    }
    ...
} 

@McpLogging 是 Spring AI MCP 注解,用于将方法注册为某个特定 MCP 客户端连接的通知处理器。在我们的示例中,处理器方法名为 handleLoggingMessage()。连接名为 password-strength-logging-server,它与服务器配置文件 application-server.properties 中为 stdio 连接配置的 ID 匹配:

$ properties
spring.ai.mcp.server.name= password-strength-logging-server

处理器的参数 notification 是 MCP 日志通知反序列化后的载荷。它的 level() 方法给出日志严重级别(例如 DEBUGERROR),而 data() 方法则包含服务器发送的实际日志内容。

我们已经看到,由于 McpSyncRequestContext 接口提供了 warn()error() 等多种日志方法,服务器可以在每次调用时选择日志级别。然而,客户端也可以设置服务器的日志级别。因此,服务器可以过滤掉低于指定日志级别的通知消息

$ java
mcpSyncClient.setLoggingLevel(McpSchema.LoggingLevel.WARNING);

这里的客户端调用会指示服务器过滤掉 WARNING 以下的所有内容。例如,服务器不会发送 DEBUGINFO 级别的通知。

6. 示例

当我们把 "weak" 作为要评估的密码发送给 MCP 服务器时,日志处理程序会输出以下日志消息列表:

Received server logging notification [WARNING]: Password shorter than recommended 12 characters
Received server logging notification [WARNING]: Password missing uppercase letters
Received server logging notification [WARNING]: Password missing digits
Received server logging notification [INFO]: Final score: 25

这个结果是符合预期的,因为密码 "weak" 长度不足 12 个字符,并且不包含大写字母和数字。它只满足“常见密码”这一判据。因此,满分 100 分它只得到 25 分。

我们的示例只包含一个 MCP 服务器和一个 MCP 客户端。但如果这个客户端是一个由 LLM 编排的代理,那么可能会发生如下一系列交互:

  • 用户 → 客户端:用户问出如下问题:“密码 weak 有多强?”
  • 客户端 → LLM:客户端将用户消息连同会话开始时从 MCP 服务器收到的工具信息一起发送给 LLM。
  • LLM → 客户端:LLM 不会自己去调用服务器上的工具,而是返回一个响应,表示它想调用工具 checkStrength,参数为 password=weak
  • 客户端 → 服务器:客户端通过 stdio 传输发出实际的 JSON-RPC 请求。
  • 服务器 → 客户端:服务器以 JSON-RPC 响应的形式返回我们之前看到的结果。
  • 客户端 → LLM:客户端将工具结果送回对话,并请 LLM 继续。
  • LLM → 用户:LLM 生成一段自然语言答案,综合来自工具的结构化结果,大致如下:“这个密码非常弱。它只有 4 个字符——你至少需要 12 个——而且它不包含任何大写字母或数字。如果你愿意,我可以给出一些建议。”

7. 结论

在本文中,我们讨论了 Spring AI 中的 MCP 日志记录。首先,我们了解了日志功能在 MCP 生态系统中是如何工作的;然后,我们考察了一个评估密码强度的 MCP 服务器上 MCP 日志记录的具体细节。我们看到,可以使用 McpSyncRequestContext 接口中的日志方法,以不同的严重级别输出日志消息。

接着,我们讨论了 MCP 客户端上的日志记录。我们了解到,可以使用带有 @McpLogging 注解的处理器来处理日志。最后,我们通过一个同时使用服务器和客户端的示例,讨论了当用户请求评估密码强度时,LLM、客户端和服务器之间的交互。

和往常一样,示例的完整源代码可在 GitHub 上获取。