MCP Server 开发实战:Spring AI 把后端日志查询暴露给 AI Agent(7 条设计原则与踩坑实录)

前言:Agent 会聊天,但不会查你的日志

让 AI Agent 参与故障排查,是 AIOps 落地里最自然的场景:值班同学对着对话窗口说一句"帮我看看 192.168.1.10 昨天上午有没有 error",Agent 就该自己去查日志、聚合统计、给出结论。但要做到这一点,后端必须把查询能力"翻译"成大模型能理解、能调用的工具------这就是 MCP(Model Context Protocol)要解决的问题。

MCP 是 Anthropic 于 2024 年底开源的模型上下文协议,如今已成为 AI Agent 接入外部系统的事实标准:服务端把能力声明成一个个"工具"(Tool),客户端(Claude Desktop、各类 Agent 平台、IDE 插件)通过统一的协议发现并调用它们。对 Java 后端来说,Spring AI 已经提供了完整的 MCP Server 支持,几个注解就能把一个 Service 方法变成 Agent 可调用的工具。

但"能调通"和"好用"之间隔着一条鸿沟。大模型不会读你的代码,它只看你写在注解里的几十字描述;它的训练数据有时间截止点,会把"5 月 6 日"解析成错的年份;它还会自作聪明地把你示例里的字段映射当成规则。这些坑,我们在日志平台的 MCP 服务(内部模块 log-mcp,SSE 协议对外)上全部踩过一遍。

本文就把这套实践完整拆开:场景与工具拆分 → Spring AI 接入 → 7 条工具设计原则 → 核心代码 → 4 个真实踩坑案例 → 上线前 Checklist 。文中代码可以直接对照落地,底层检索 DSL 的写法可以参考我之前的《Elasticsearch 日志检索 DSL 实战:时间范围查询、字段去重、分钟级统计与最新日志获取》,平台整体架构见《云原生日志采集与检索架构实战》。

一、场景:把"查日志"拆成 Agent 能驾驭的 5 个工具

我们的日志平台承载着多套系统的日志检索,人用的查询界面背后是一套 ES 检索服务。要做 Agent 入口时,第一反应可能是"暴露一个大而全的查询接口"------这恰恰是灾难的开始(原因见原则 1)。最终的拆分方案是 5 个工具:

工具 分类 职责
queryLogs 核心工具 执行日志内容查询,返回结构化结果
getCurrentTime 前置工具 提供当前系统时间基准
buildKqlFilter 前置工具 把 KQL 关键词表达式转成 filter_query JSON
getLogIndexFields 辅助工具 获取索引可用字段列表
getLogSourceList 辅助工具 获取日志来源列表

三类工具的定位:

  • 核心工具:必调,完成主逻辑,一次对话里通常只调它;
  • 前置工具:核心工具的依赖,Agent 必须先调它们才能正确组装参数(时间基准、过滤条件);
  • 辅助工具:按需调用,帮 Agent 确认"字段存不存在""有哪些日志来源"这类不确定信息。

一次典型调用的完整链路是:getCurrentTime(对齐时间)→ getLogIndexFields(确认字段)→ buildKqlFilter(组装过滤条件)→ queryLogs(查询)→ 格式化返回。工具间的依赖顺序不是靠 Agent 猜,而是全部写死在 description 里(原则 4)。

二、五分钟接入:Spring AI MCP Server

2.1 Maven 依赖

xml 复制代码
<!-- Spring AI 的 MCP 服务端 starter(WebMVC 版本) -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

@McpTool / @McpToolParam 注解来自 spring-ai-community 的 mcp-annotations 模块,随 starter 一并引入,无需额外配置。

2.2 配置文件

yaml 复制代码
# application-mcp.yaml
spring:
  ai:
    mcp:
      server:
        protocol: sse          # 通信协议:SSE(Server-Sent Events)
        sse-endpoint: /sse-log # SSE 端点路径,外部 AI 平台通过此地址连接

两个配置项的取舍:

配置项 值 说明
protocol sse 外部 AI 平台通过 HTTP 长连接订阅工具列表和调用结果,适合服务端单向推送场景
sse-endpoint /sse-log 自定义端点路径,避免与同主机上其他 MCP 服务冲突

2.3 启动类扫描

确保启动类的 @ComponentScan(或 @SpringBootApplication 的默认扫描)能覆盖到 MCP 工具所在的包(如 com.example.logmcp),并通过 spring.profiles.include 或自动扫描让 application-mcp.yaml 生效。之后外部平台访问 http://<host>:<port>/sse-log 建立 SSE 连接,就能发现并调用本服务暴露的全部 MCP 工具。

2.4 核心注解速览

java 复制代码
@McpTool(description = "...")        // 标记方法为 MCP 工具
@McpToolParam(description = "...")   // 标记参数,向大模型说明含义与格式

两个注解看起来朴素,但后文会反复强调:description 是大模型调用你工具的唯一依据,它的每一个字都在参与"编程"。

三、7 条工具设计原则

原则 1:原子化,最小单元

每个 MCP 工具只干一件事,且干到极致。 不要把多个职责塞进一个工具里。

java 复制代码
// ❌ 错误:一个工具既查日志又查字段又查来源,大模型无法判断该传哪些参数
@McpTool(description = "查询日志、字段、来源...")
public String queryEverything(...) { ... }

// ✅ 正确:拆分为独立工具,每个工具职责单一
@McpTool(description = "查询系统日志...") public String queryLogs(...) { ... }
@McpTool(description = "获取日志索引字段列表...") public String getLogIndexFields(...) { ... }
@McpTool(description = "获取日志来源列表...") public String getLogSourceList(...) { ... }

好处非常实际:

  • 大模型调用意图清晰,不会传错参数;
  • 工具描述简洁,token 占用少,大模型更容易理解;
  • 便于独立测试、独立复用;
  • 失败时定位问题快------哪个工具出错一目了然。

原则 2:描述即契约

大模型只能看到你写在 @McpTool 和 @McpToolParam description 里的文字,它不会读你的代码。description 就是唯一的调用依据,怎么强调都不过分。

一个真实的反面教材------为了"方便",在描述里列举了常用字段:

java 复制代码
// ❌ 错误:给出字段列表,暗示大模型可以自行推断映射
"常用字段包括:level(日志级别)、host_ip(目标IP)、raw_message(日志内容)..."

大模型看到后会自行联想:用户说"错误日志" → 自动映射为 level: ERROR。结果查询不到数据,因为底层对无字段关键词默认走 raw_message 全文匹配,而该字段的真实取值未必是大写。正确做法是把规则写死、把兜底行为讲明白:

java 复制代码
// ✅ 正确:明确规则,禁止推断
"用户没有明确指定'字段名:值'格式时,只传纯关键词,不要擅自推断字段。"
"无字段的关键词会自动匹配日志全文(raw_message),无需也不应额外指定字段。"

原则 3:前置工具优先

大模型存在训练数据截止日期,对"当前时间"的认知可能是错的。用户说"查 5 月 6 日的日志",大模型完全可能解析成上一年或训练数据里的默认年份。

必须提供 getCurrentTime 前置工具,并在所有涉及时间的工具描述中反复强调:

java 复制代码
@McpTool(description = "...Agent必须先调用 getCurrentTime 获取当前系统时间作为基准,"
        + "再解析生成 yyyy-MM-dd HH:mm:ss.SSS 格式的时间参数。")

注意一个细节:不要在 description 里写死具体年份(如"当前是 2026 年"),到明年它就变成新的错误来源。

原则 4:执行顺序显性化

工具间存在依赖时(A 必须在 B 之前调用),必须在 description 中写明步骤编号,靠大模型自己推断调用顺序是不可靠的:

java 复制代码
// ✅ 正确:明确步骤依赖
"如果用户提到了字段概念但没用'字段:值'格式,必须先完成第4步调用 getLogIndexFields,"
+ "确认后再调用 buildKqlFilter 构建 KQL。"

原则 5:参数防空兜底

下游服务可能对入参 Map 的 value 直接调用 .toString(),传入 null 就是裸 NPE------而且这发生在 Agent 调用链的最深处,报错信息对大模型毫无可读性。MCP 层组装参数时,必须保证所有参数都有非 null 默认值:

java 复制代码
// ✅ 正确:无条件 put,空值用默认值填充
queryParam.put("logId", "ALL");
queryParam.put("field", "log_time");
queryParam.put("order", "log_time desc");
queryParam.put("hostIp", StringUtils.hasText(hostIp) ? hostIp : "");
queryParam.put("system", StringUtils.hasText(systemCode) ? systemCode : "");
queryParam.put("filter_query", StringUtils.hasText(filterQuery) ? filterQuery : "");
queryParam.put("logSource", StringUtils.hasText(logSource)
        ? Collections.singletonList(logSource) : Collections.emptyList());
queryParam.put("tag", Collections.emptyList());
queryParam.put("tenantCode", "system");

原则 6:工具分类清晰

在对外文档(SKILL 文档,见第六节)里给工具打上"核心 / 前置 / 辅助"标签,帮大模型理解调用优先级:核心必调,前置先行,辅助按需。分类本身就是一种提示工程。

原则 7:描述与文档三方一致

@McpTool description、@McpToolParam description、SKILL 文档(SKILL-xxx.md)必须保持三方一致,任何一方更新后其他两方同步修改。不一致的后果很隐蔽:大模型从文档学到一套规则、从工具描述学到另一套,行为随机分裂------这次遵守那次违反,排查起来极其折磨。

四、核心工具完整实现

4.1 最简单的工具:getCurrentTime

java 复制代码
package com.example.logmcp;

import com.example.logmcp.util.McpLogToolHelper;
import lombok.extern.slf4j.Slf4j;
import org.springaicommunity.mcp.annotation.McpTool;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class McpCurrentTimeService {

    @McpTool(description = "获取当前系统时间。当用户查询涉及相对时间或自然语言描述的时间时,"
                    + "Agent应先调用此工具获取当前时间作为基准,再解析用户意图。\n"
                    + "例如用户说'5月6日上午9点',Agent应先获取当前时间,"
                    + "再推断准确的年份并生成 yyyy-MM-dd HH:mm:ss.SSS 格式的时间参数。\n"
                    + "返回格式:yyyy-MM-dd HH:mm:ss.SSS")
    public String getCurrentTime() {
        String currentTime = McpLogToolHelper.getCurrentTime();
        log.info("MCP获取当前时间:currentTime={}", currentTime);
        return currentTime;
    }
}

注意 description 里的写法:给了一个具体的自然语言例子("5月6日上午9点"),并把期望的输出格式写在最后。举例子比讲道理有效得多,这是写给大模型看的 few-shot。

4.2 核心工具:queryLogs

java 复制代码
@Slf4j
@Service
public class McpLogQueryService {

    @Autowired
    private ILogRetrievalService logRetrievalService;

    @McpTool(description = "查询系统日志。当用户想查看日志、排查故障、分析系统问题时调用。\n"
                    // 前置依赖说明
                    + "如果用户查询涉及相对时间或自然语言描述的时间,"
                    + "Agent必须先调用 getCurrentTime 获取当前系统时间作为基准,"
                    + "再解析生成 yyyy-MM-dd HH:mm:ss.SSS 格式的时间参数。\n"
                    // 参数约束
                    + "hostIp 和 systemCode 至少提供一个。\n"
                    // 过滤条件依赖说明
                    + "如需对日志内容进行过滤,请先调用 buildKqlFilter 工具"
                    + "将自然语言转换为 filterQuery,再传入本工具。\n"
                    // 字段查询依赖说明
                    + "如果用户查询涉及特定字段条件(如'ERROR级别''状态码500'),"
                    + "且该字段不是 queryLogs 已知参数(如 hostIp、systemCode、logSource),"
                    + "必须先调用 getLogIndexFields 获取可用字段列表,确认字段存在后再构建过滤条件。\n"
                    // 日志来源依赖说明
                    + "如果用户想按日志来源筛选,可先调用 getLogSourceList 获取可用来源列表,再传入 logSource 参数。\n"
                    + "pageSize 最大限制为 100 条。")
    public String queryLogs(
            @McpToolParam(description = "目标IP地址,如 192.168.1.1") String hostIp,
            @McpToolParam(description = "系统编码,映射到system字段过滤。若Agent已通过CMDB查得编码则直接传入") String systemCode,
            @McpToolParam(description = "开始时间,格式 yyyy-MM-dd HH:mm:ss.SSS,如 2026-05-06 00:00:00.000") String startTime,
            @McpToolParam(description = "结束时间,格式 yyyy-MM-dd HH:mm:ss.SSS,如 2026-05-06 23:59:59.999") String endTime,
            @McpToolParam(description = "日志内容过滤条件,filter_query JSON 格式。\n"
                    + "由 buildKqlFilter 工具将 KQL 表达式转换得到,直接传入即可。\n"
                    + "如不填则查询所有日志内容。") String filterQuery,
            @McpToolParam(description = "页码,从1开始,不填默认为1") Integer pageNum,
            @McpToolParam(description = "每页返回条数,不填默认为50,最大不超过100") Integer pageSize,
            @McpToolParam(description = "日志来源筛选,如 /var/log/nginx/access.log。不填则查询所有来源。\n"
                    + "可从 getLogSourceList 获取该时间段内的可用来源列表。") String logSource
    ) {
        // 参数校验:失败时返回可读的错误提示,而不是抛异常
        if (!StringUtils.hasText(hostIp) && !StringUtils.hasText(systemCode)) {
            return "请提供IP地址或系统编码至少一项。";
        }
        if (!StringUtils.hasText(startTime) || !StringUtils.hasText(endTime)) {
            return "请提供查询时间范围(开始时间和结束时间)。";
        }

        int currentPage = (pageNum != null && pageNum > 0) ? pageNum : 1;
        int currentSize = (pageSize != null && pageSize > 0) ? pageSize : 50;
        if (currentSize > 100) {
            currentSize = 100;
        }

        Map<String, Object> queryParam = buildQueryParam(hostIp, systemCode,
                startTime, endTime, filterQuery, currentPage, currentSize, logSource);

        LogPageResult result;
        try {
            result = logRetrievalService.logQuery(queryParam);
        } catch (Exception e) {
            log.error("MCP日志查询异常", e);
            return "日志查询发生异常:" + e.getMessage();
        }

        return McpLogToolHelper.formatResult(result, currentPage, currentSize);
    }

    /**
     * 组装查询参数 ------ 所有参数必须设置非 null 默认值(原则 5)
     */
    private Map<String, Object> buildQueryParam(String hostIp, String systemCode,
                                                 String startTime, String endTime,
                                                 String filterQuery, int from, int size,
                                                 String logSource) {
        Map<String, Object> queryParam = new HashMap<>();

        queryParam.put("logId", "ALL");
        queryParam.put("field", "log_time");
        queryParam.put("from", from);
        queryParam.put("size", size);
        queryParam.put("order", "log_time desc");  // 带字段的排序,避免歧义
        queryParam.put("hostIp", StringUtils.hasText(hostIp) ? hostIp : "");
        queryParam.put("system", StringUtils.hasText(systemCode) ? systemCode : "");
        queryParam.put("startTime", startTime);
        queryParam.put("endTime", endTime);
        queryParam.put("filter_query", StringUtils.hasText(filterQuery) ? filterQuery : "");
        queryParam.put("logSource", StringUtils.hasText(logSource)
                ? Collections.singletonList(logSource) : Collections.emptyList());
        queryParam.put("tag", Collections.emptyList());
        queryParam.put("tenantCode", "system");

        return queryParam;
    }
}

值得注意的三个细节:

  1. description 里用注释分段(前置依赖/参数约束/依赖说明),源码可读,大模型读到的也是分好段的规则;
  2. 参数校验失败返回中文提示而非抛异常------这段文字会直接回到大模型上下文里,它读到"请提供IP地址或系统编码至少一项"后会自己补参数重试;
  3. 异常兜底拼上 e.getMessage(),让大模型有机会理解失败原因并换个方式重试。

4.3 辅助类:McpLogToolHelper

java 复制代码
public class McpLogToolHelper {

    /** 获取默认开始时间(当天 00:00:00.000) */
    public static String getDefaultStartTime() {
        return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + " 00:00:00.000";
    }

    /** 获取默认结束时间(当天 23:59:59.999) */
    public static String getDefaultEndTime() {
        return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + " 23:59:59.999";
    }

    /** 获取当前系统时间(精确到毫秒) */
    public static String getCurrentTime() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
    }
}

五、KQL 转换工具:把"描述即契约"用到极致

buildKqlFilter 负责把自然语言风格的关键词表达式(KQL)转成底层检索服务需要的 filter_query JSON。它是"大模型擅自推断字段"问题的主战场,description 的写法非常有代表性:

java 复制代码
@Slf4j
@Service
public class McpKqlConverterService {

    @McpTool(description = "将自然语言风格的关键词查询表达式(KQL)转换为 filter_query JSON 格式。"
                    + "当用户查询涉及日志内容过滤条件时,Agent应先调用此工具生成 filterQuery,再传入 queryLogs 进行查询。\n"
                    + "\n"
                    // ⚠️ 核心约束必须写在最前面
                    + "【重要规则】\n"
                    + "1. 用户没有明确指定\"字段名:值\"格式时(如只说\"错误日志\"\"查timeout\"),"
                    + "只传纯关键词,不要擅自推断字段。"
                    + "无字段的关键词会自动匹配日志全文(raw_message),无需也不应额外指定字段。\n"
                    + "2. 只有用户明确说了\"字段:值\"格式(如\"level: ERROR\")时,才保留字段名。\n"
                    + "3. 如果用户提到了字段概念但没用\"字段:值\"格式(如\"按级别查ERROR\"\"按IP查192.168\"),"
                    + "必须先调用 getLogIndexFields 获取真实字段名,确认字段存在后再构建 KQL。\n"
                    + "\n"
                    + "KQL 语法规则:\n"
                    + "1. 关键词直接用单词,如:error, exception, timeout\n"
                    + "2. '和/且/并且' 用 and 连接:error and exception\n"
                    + "3. '或/或者' 用 or 连接:error or warn\n"
                    + "4. '不包含/不是' 用 not:not timeout\n"
                    + "5. 含空格的关键词用双引号:\"connection refused\"\n"
                    + "6. 多个条件组合:error and exception and timeout\n"
                    + "\n"
                    // ⚠️ 示例只展示纯关键词写法,不要展示带字段的示例
                    + "自然语言 → KQL 示例:\n"
                    + "• '查包含error和exception的日志' → error and exception\n"
                    + "• '查不包含timeout的' → not timeout\n"
                    + "• '查connection refused相关' → \"connection refused\"\n"
                    + "• '查有NullPointerException的' → NullPointerException")
    public String buildKqlFilter(
            // ⚠️ 参数描述同样避免带字段的示例
            @McpToolParam(description = "KQL 查询表达式,由Agent从用户自然语言中提取关键词和逻辑关系后转换。"
                    + "只传纯关键词,不要擅自添加字段。例如:error and exception、not timeout、NullPointerException 等。"
            ) String kqlExpression
    ) {
        // KQL → filter_query JSON 的转换逻辑(词法分析 + 布尔组合),略
    }
}

这段 description 的三处心机:

  1. 【重要规则】放在最前面------大模型对 description 开头的内容注意力最强;
  2. 语法规则用编号穷举,六条覆盖全部合法写法,不留给猜测空间;
  3. 示例刻意只给纯关键词形态,没有一个带字段的示例------你展示什么,大模型就模仿什么。

六、配套 SKILL 文档:给 Agent 的"使用说明书"

工具 description 负责"单点规则",跨工具的完整流程则写在 SKILL 文档里(多数 Agent 平台支持挂载技能文档,与 MCP 工具描述互补)。我们的 SKILL-log-query.md 核心是这份执行步骤:

markdown 复制代码
## 执行步骤

1. **解析用户意图**:从用户输入中提取关键参数信息(IP、系统编码、时间范围、
   关键词过滤条件、日志来源等)。
2. **获取时间基准**:如果用户时间描述涉及相对时间或自然语言(如"5月6日""上午9点"
   "最近半小时"),**必须先调用 `getCurrentTime` 获取当前系统时间作为基准**,
   再据此推断准确的年份并解析为 `yyyy-MM-dd HH:mm:ss.SSS` 格式。
3. **转换过滤条件(可选)**:如果用户提供了自然语言风格的关键词过滤条件,
   调用 `buildKqlFilter` 将 KQL 表达式转换为 `filter_query` JSON。
   - **规则**:用户没有明确指定`字段名:值`格式时,只传纯关键词,**不要擅自推断字段**。
     无字段的关键词会自动匹配日志全文(raw_message)。
   - 如果用户提到了字段概念但没用`字段:值`格式,**必须先完成第4步调用
     `getLogIndexFields` 获取真实字段名**,确认后再调用 `buildKqlFilter` 构建 KQL。
4. **获取字段辅助(可选)**:如果用户查询涉及特定字段条件(如"ERROR级别""状态码500"),
   且该字段不是 `queryLogs` 已知参数(如 hostIp、systemCode、logSource),
   先调用 `getLogIndexFields` 获取可用字段名和类型,确认字段存在后再构建过滤条件。
5. **获取日志来源(可选)**:如果用户想按日志来源筛选,先调用 `getLogSourceList`
   获取可用来源列表。
6. **组装参数并查询**:将解析/转换后的参数组装,调用 `queryLogs` 执行日志查询。
7. **返回结果**:将查询到的日志列表格式化后返回给用户。

注意步骤 3 和步骤 4 的交叉引用写法------"必须先完成第4步"这类显式编号,就是原则 4 的落地。

七、踩坑实录:4 个真实案例

案例 1:大模型时间解析错误

现象:用户说"查 5 月 6 日的日志",大模型传了错误的年份,查了个寂寞。

根因:大模型训练数据有截止日期,默认年份停留在训练语料的"最近一年"。

修复 :提供 getCurrentTime 前置工具;在所有时间相关工具的 description 中强调"必须先调用 getCurrentTime 获取当前系统时间作为基准";不在 description 里写死具体年份。

案例 2:大模型擅自推断字段

现象 :用户说"查错误日志",大模型给 buildKqlFilter 传了 level: ERROR,结果一条都查不到。

根因:早期版本的工具 description 里列举了"常用字段",大模型看到字段名就自行联想映射,完全无视底层默认走全文匹配的事实。

修复:删掉 description 里的"常用字段列表";删掉所有带字段的 KQL 示例;写明规则"用户没指定字段名:值格式时,只传纯关键词";写明兜底行为"无字段的关键词会自动匹配 raw_message"。

案例 3:下游服务 NPE

现象 :queryLogs 调用检索服务时偶发空指针,Agent 收到一长串 Java 堆栈,无法自我修复。

根因 :下游服务对入参 Map 的 value 直接 .toString(),value 为 null 或 key 缺失即 NPE。

修复 :MCP 层 buildQueryParam 对所有参数无条件 put,null 一律替换为 Collections.emptyList() 或 ""。原则 5 由此而来。

案例 4:工具描述与文档不一致

现象:Agent 行为不稳定,同一句话有时遵守规则有时不遵守。

根因 :@McpTool description、@McpToolParam description、SKILL 文档三方内容漂移------改了代码忘了同步文档,大模型从两个渠道学到两套规则。

修复:建立修改 checklist,任何一方变更时同步更新另外两方。

八、最佳实践 Checklist

新增或修改 MCP 工具前,逐条确认:

  • 描述是否足够明确? 大模型能否仅凭 description 正确调用?
  • 是否有前置依赖? 若有,是否在 description 中明确说明调用顺序?
  • 时间参数是否要求先调 getCurrentTime? 涉及时间的工具都要写。
  • 是否列举了误导性字段/示例? 避免大模型自行推断。
  • 参数是否有非 null 默认值? 防止下游 NPE。
  • 返回值是否格式化? 返回给大模型的文本应清晰、结构化。
  • 三方描述是否一致? @McpTool、@McpToolParam、SKILL 文档是否同步?
  • 是否提供了错误提示? 参数校验失败时,返回明确的错误信息而非抛异常。

结语

MCP 把"后端接口"升级成了"大模型的 API",但协议只解决了通信问题,好不好用取决于你写给大模型的那几十字 description。回头看这 7 条原则,本质上都是在回答同一个问题:当一个不会读代码、没有常识、只有上下文的调用方来使用你的服务时,契约应该怎么写? 原子化降低选择难度,描述即契约消除歧义,前置工具补齐时间基准,参数兜底守住失败底线。

把这套思路迁移到其他场景(查监控指标、执行运维工单、检索知识库)是一样的打法:先拆工具,再写"给大模型看的说明书",最后用真实对话回灌调优。

你在 MCP 工具的 description 上踩过什么坑?欢迎在评论区交流------特别是"大模型不按套路传参"的案例,攒得多了我整理一篇进阶篇。


相关推荐
大牧师1 小时前
Mybatis 框架教程
java·数据库·mybatis·数据持久化·orm
SunnyDays10111 小时前
Java 合并 PDF 文件:完整合并、指定页面合并与流合并
java·开发语言·合并pdf
阿_旭1 小时前
【AI前沿】GTR:一套网络通吃检测/分割/姿态/深度六大任务,对标YOLO26
人工智能
AI你一生一世1 小时前
当一句自然语言指令穿透三层抽象:从“把按钮改成蓝色“看 AI 编码代理的真实边界
人工智能·自然语言处理·前端架构·技术债·设计令牌·ai编码代理·代码抽象
袋鼠云数栈1 小时前
非结构化数据也能“周期调度“:离线开发BatchWorks的多模态数据同步实践
大数据·人工智能·多模态数据·离线开放
算了吧95691 小时前
GEO服务商选型指南:2026年企业评估框架与决策路径
大数据·人工智能·物联网
workflower1 小时前
矿用机器人人工智能安全与治理体系
人工智能·安全·机器学习·机器人·云计算·无人机
tianbin9111 小时前
从模型微调到API调用:大模型训练后的高效工程化落地路径
人工智能