从 0 到 商用:AI Agent x SKILL x MCP 全栈实战教程:L2 高等篇:MCP 协议 + Spring AI + Agent 编排

L2 高等篇:MCP 协议 + Spring AI + Agent 编排

》本篇目标:理解 MCP 协议细节,能用 Java 写生产级 MCP Server,

》把 Spring 业务系统接入 Spring AI,用 LangChain4j 做客户端,

》掌握 3 种主流 Agent 编排模式。


5. MCP 协议深度解读

5.1 协议基础:JSON-RPC 2.0

MCP 跑在 JSON-RPC 2.0 之上(MCP-1)。一个 MCP 消息长这样:

请求(Request):

json 复制代码
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "add",
    "arguments": {"a": 3, "b": 5}
  }
}

响应(Response):

json 复制代码
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{"type": "text", "text": "3 + 5 = 8"}],
    "isError": false
  }
}

通知(Notification,无响应):

json 复制代码
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {"token": "abc", "value": 50}
}

错误(Error Response):

json 复制代码
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": {"field": "a", "reason": "must be integer"}
  }
}

标准错误码(与 JSON-RPC 一致):

Code 含义 何时用
-32700 Parse error JSON 解析失败
-32600 Invalid Request 不是合法 JSON-RPC
-32601 Method not found 方法名不存在
-32602 Invalid params 参数不合法
-32603 Internal error 服务器内部错误
-32000 ~ -32099 Server error 服务端自定义错误

5.2 协议生命周期

复制代码
Client                              Server
  |                                    |
  |--- initialize (capabilities) ----->|  <- 1. 握手,交换能力
  |<-- initialize result --------------|
  |--- initialized (notification) ---->|  <- 2. 客户端确认
  |                                    |
  |--- tools/list -------------------->|  <- 3. 正常业务
  |<-- tools (list) -------------------|
  |--- resources/read ---------------->|
  |<-- resource content ---------------|
  |--- tools/call -------------------->|
  |<-- tool result --------------------|
  |                                    |
  |     anytime                        |
  |--- notifications/... ------------->|  <- 4. 异步通知
  |--- ping -------------------------->|  <- 5. 心跳
  |<-- pong ---------------------------|
  |                                    |
  |--- shutdown ---------------------->|  <- 6. 关闭

关键点:

  • initialize 是强制的,必须先于其他调用
  • initialized 是 notification(无 id)
  • 所有 list/call/read 都需要 request/response
  • notifications/* 是单向通知
  • ping 用于心跳和健康检查

5.3 Capability 协商

Client 能力:

json 复制代码
{
  "capabilities": {
    "sampling": {},     // 客户端支持 LLM 采样
    "roots": {          // 客户端能提供文件系统根
      "listChanged": true
    },
    "experimental": {"feature-x": {}}
  }
}

Server 能力:

json 复制代码
{
  "capabilities": {
    "tools": {"listChanged": true},      // 暴露工具
    "resources": {"subscribe": true},    // 暴露资源
    "prompts": {"listChanged": false},   // 暴露提示模板
    "logging": {},                       // 接受日志
    "completions": {},                   // 接受补全
    "experimental": {"feature-y": {}}
  }
}

Client 不知道的 Capability 不要调 。比如 Server 没声明 tools: {},Client 调 tools/list 会返回 Method not found.

5.4 3 种传输方式(Transport)

A. Stdio(本地子进程,最常用)
复制代码
+-------------------+
|   Client App      |
|                   |
|   stdin  ->  +-------------+
|             |  MCP Server |  (java/python/node 进程)
|   stdout <-  \--------------+
+-------------------+
  • 适合:本地工具,本地数据,开发环境
  • 优点:零网络配置,安全(沙箱)
  • 缺点:不能跨主机
  • 典型场景:读本地文件,查本地数据库,执行本地脚本
B. Streamable HTTP(2025 新标准,推荐远程用)
复制代码
Client                            Server
  |   POST /mcp (request)                |
  |------------------------------------->|
  |   HTTP 200 + SSE stream (response)   |
  |<-------------------------------------|
  |                                      |
  |   GET /mcp (SSE 通道,保持长连接)       |
  |<=====================================|
  |   SSE event: server request          |  <- 服务端可以主动发请求(如 sampling)
  |                                      |
  • 适合:远程工具,SaaS 化,跨主机
  • 优点:双向通信,HTTP 友好(防火墙友好)
  • 缺点:需要部署 HTTP 服务器
  • 注意:已替代旧的 HTTP+SSE 传输
C. SSE(旧标准,已废弃)
  • 2025 之前的方案:GET /sse 拿服务端事件,POST /messages 发请求
  • 新部署请直接用 Streamable HTTP
选型决策表
场景 推荐
本地工具(读文件,查 DB) Stdio
远程 SaaS API 包装 Streamable HTTP
高频双向通信 Streamable HTTP
一次性命令行调用 Stdio
Web 应用内嵌 Streamable HTTP
移动端 Streamable HTTP

5.5 Resources:可读取的数据

Resource 标识(URI):

复制代码
file:///home/user/doc.txt
db://postgres/customers
https://api.example.com/v1/users

声明 Resource:

json 复制代码
{
  "uri": "file:///etc/hosts",
  "name": "Hosts File",
  "description": "系统 hosts 文件",
  "mimeType": "text/plain"
}

读取:

json 复制代码
// Request
{"method": "resources/read", "params": {"uri": "file:///etc/hosts"}}
// Response
{
  "contents": [{
    "uri": "file:///etc/hosts",
    "mimeType": "text/plain",
    "text": "127.0.0.1 localhost\n..."
  }]
}

订阅(Server 主动推送变化):

json 复制代码
// 客户端订阅
{"method": "resources/subscribe", "params": {"uri": "file:///var/log/app.log"}}
// 服务端推送
{"method": "notifications/resources/updated", "params": {"uri": "file:///var/log/app.log"}}

5.6 Tools:可调用的函数

声明 Tool:

json 复制代码
{
  "name": "search_docs",
  "description": "Search internal documentation",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": {"type": "string", "description": "搜索关键词"},
      "max_results": {"type": "integer", "default": 5, "minimum": 1, "maximum": 50}
    },
    "required": ["query"]
  }
}

调用:

json 复制代码
// Request
{
  "method": "tools/call",
  "params": {
    "name": "search_docs",
    "arguments": {"query": "Spring AI MCP", "max_results": 3}
  }
}
// Response
{
  "content": [
    {"type": "text", "text": "Found 3 results:\n1. ..."}
  ],
  "isError": false
}

Tool 结果类型:

  • text:纯文本
  • image:base64 编码图片
  • audio:base64 编码音频
  • resource:内嵌 resource

关键设计原则:

  1. Tool 名字要清晰(模型靠 name + description 选工具)
  2. Description 要说明何时用 / 何时不用(减少模型选错)
  3. 参数尽量少(>10 个参数模型选不对)
  4. 返回内容要简洁(token 是钱)
  5. 错误要明确 (返回 isError: true + 错误信息)

5.7 Prompts:可复用的提示模板

json 复制代码
{
  "name": "code-review",
  "description": "Code review with security focus",
  "arguments": [
    {"name": "language", "description": "编程语言", "required": true},
    {"name": "code", "description": "代码片段", "required": true}
  ]
}

获取渲染后的 Prompt:

json 复制代码
// Request
{"method": "prompts/get", "params": {"name": "code-review", "arguments": {...}}}
// Response
{
  "description": "Code review with security focus",
  "messages": [
    {
      "role": "user",
      "content": {
        "type": "text",
        "text": "请用 ${language} 安全最佳实践审查以下代码:\n\n${code}\n\n关注:注入,XSS,权限..."
      }
    }
  ]
}

5.8 Sampling:让 Server 调 Client 的 LLM

这是 MCP 的杀手级特性 -- Server 可以反向调用 Client 的 LLM.

复制代码
Server: "我需要 LLM 帮用户总结这条日志"
  -> 发 sampling/createMessage 请求给 Client
Client: 调用自家 LLM,把结果返回
Server: 把结果嵌入 tool response

典型场景:

  • 工具调用后,Server 想"美化"一下结果给用户
  • Server 想要 LLM 决策下一步该调哪个子工具
  • Server 想让 LLM 帮用户生成查询 SQL

声明(Server):

json 复制代码
{"capabilities": {"sampling": {}}}

Request:

json 复制代码
{
  "method": "sampling/createMessage",
  "params": {
    "messages": [{"role": "user", "content": {"type": "text", "text": "总结这条日志"}}],
    "modelPreferences": {"hints": [{"name": "fast"}]},
    "maxTokens": 100
  }
}

5.9 Roots:客户端能提供的文件系统根

声明:

json 复制代码
{"method": "roots/list"}
// Response
{
  "roots": [
    {"uri": "file:///home/user/projects", "name": "Projects"}
  ]
}

用途:Server 知道客户端授权访问哪些目录。


6。用 Java MCP SDK 写一个完整 Server

6.1 项目结构

复制代码
mcp-fs-server/
+--- pom.xml
\--- src/main/java/com/example/mcpfs/
    +--- McpFsServer.java       # 启动入口
    +--- FsTools.java            # 工具实现
    \--- FsResources.java        # 资源实现

6.2 pom.xml

xml 复制代码
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>mcp-fs-server</artifactId>
  <version>1.0.0</version>

  <properties>
    <maven.compiler.source>21</maven.compiler.source>
    <mcp.version>0.10.0</mcp.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>io.modelcontextprotocol</groupId>
      <artifactId>mcp-server</artifactId>
      <version>${mcp.version}</version>
    </dependency>
    <dependency>
      <groupId>io.modelcontextprotocol</groupId>
      <artifactId>mcp-json-jackson2</artifactId>
      <version>${mcp.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>2.0.13</version>
    </dependency>
  </dependencies>
</project>

6.3 FsTools.java

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

import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.spec.McpSchema.*;

import java.nio.file.*;
import java.util.*;

public class FsTools {

    public static McpServerFeatures.SyncToolSpecification readFileTool(Path allowedRoot) {
        return McpServerFeatures.SyncToolSpecification.builder()
            .tool(Tool.builder()
                .name("read_file")
                .description("读取指定路径的文件内容.路径必须在允许的根目录内.")
                .inputSchema(Map.of(
                    "type", "object",
                    "properties", Map.of(
                        "path", Map.of("type", "string", "description", "相对路径")
                    ),
                    "required", List.of("path")
                ))
                .build())
            .callHandler((exchange, args) -> {
                try {
                    String rel = (String) args.get("path");
                    Path abs = allowedRoot.resolve(rel).normalize();
                    if (!abs.startsWith(allowedRoot)) {
                        return new CallToolResult("错误:路径越界", true);
                    }
                    String content = Files.readString(abs);
                    return new CallToolResult(content, false);
                } catch (Exception e) {
                    return new CallToolResult("读取失败: " + e.getMessage(), true);
                }
            })
            .build();
    }

    public static McpServerFeatures.SyncToolSpecification listDirTool(Path allowedRoot) {
        return McpServerFeatures.SyncToolSpecification.builder()
            .tool(Tool.builder()
                .name("list_dir")
                .description("列出目录内容")
                .inputSchema(Map.of(
                    "type", "object",
                    "properties", Map.of(
                        "path", Map.of("type", "string", "default", ".")
                    )
                ))
                .build())
            .callHandler((exchange, args) -> {
                try {
                    String rel = (String) args.getOrDefault("path", ".");
                    Path abs = allowedRoot.resolve(rel).normalize();
                    if (!abs.startsWith(allowedRoot)) {
                        return new CallToolResult("错误:路径越界", true);
                    }
                    StringBuilder sb = new StringBuilder();
                    try (var stream = Files.list(abs)) {
                        stream.sorted().forEach(p ->
                            sb.append(p.getFileName()).append("\n"));
                    }
                    return new CallToolResult(sb.toString(), false);
                } catch (Exception e) {
                    return new CallToolResult("列出失败: " + e.getMessage(), true);
                }
            })
            .build();
    }
}

6.4 McpFsServer.java

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

import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema.*;

import java.nio.file.Path;
import java.nio.file.Paths;

public class McpFsServer {
    public static void main(String[] args) {
        // 从环境变量获取允许的根目录,默认 ~/mcp-workspace
        String rootEnv = System.getenv().getOrDefault("MCP_FS_ROOT",
            System.getProperty("user.home") + "/mcp-workspace");
        Path allowedRoot = Paths.get(rootEnv).toAbsolutePath().normalize();

        var transport = new StdioServerTransportProvider();
        McpSyncServer server = McpServer.sync(transport)
            .serverInfo("fs-server", "1.0.0")
            .capabilities(ServerCapabilities.builder()
                .tools(true)
                .resources(true)
                .build())
            .build();

        server.addTool(FsTools.readFileTool(allowedRoot));
        server.addTool(FsTools.listDirTool(allowedRoot));

        // 资源:暴露根目录本身
        server.addResource(Resource.builder()
            .uri("file://" + allowedRoot)
            .name("Workspace Root")
            .description("允许访问的工作目录根")
            .mimeType("inode/directory")
            .build());

        // 阻塞主线程
        Thread.currentThread().join();
    }
}

6.5 打包 + 测试

bash 复制代码
mvn clean package
# 配置 Hermes:
# ~/.hermes/config.yaml
# mcp:
#   servers:
#     - name: fs
#       command: ["java", "-jar", "/path/to/mcp-fs-server-1.0.0.jar"]
#       env: {MCP_FS_ROOT: "/home/user/projects"}
#       transport: stdio

7. Spring AI 完整集成

7.1 依赖

xml 复制代码
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-bom</artifactId>
      <version>1.0.0-M6</version>  <!-- 截至 2025 末最新 -->
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
  </dependency>
</dependencies>

7.2 配置 application.yml

yaml 复制代码
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: https://api.deepseek.com
      chat:
        options:
          model: deepseek-chat
          temperature: 0.7

    mcp:
      # 客户端:连接外部 MCP Server
      client:
        enabled: true
        stdio:
          servers:
            - name: fs
              command: java
              args: ["-jar", "/opt/mcp/fs-server.jar"]
              env: {MCP_FS_ROOT: "/data/workspace"}

      # 服务端:把本应用的能力暴露为 MCP
      server:
        enabled: true
        stdio: true   # 启动时通过 stdio 提供
        name: my-spring-ai-server
        version: 1.0.0

7.3 注解式 Tool 开发

java 复制代码
@Component
public class DatabaseTools {

    @Autowired
    private CustomerRepository repo;

    @Tool(description = "根据客户 ID 查询客户信息")
    public Customer getCustomer(
        @ToolParam(description = "客户 ID,整数") Long id) {
        return repo.findById(id).orElse(null);
    }

    @Tool(description = "搜索名字包含关键字的客户")
    public List<Customer> searchByName(
        @ToolParam(description = "搜索关键字") String keyword) {
        return repo.findByNameContaining(keyword);
    }

    @Tool(description = "创建新客户.返回新客户的 ID.")
    public Long createCustomer(
        @ToolParam(description = "客户姓名") String name,
        @ToolParam(description = "邮箱") String email) {
        Customer c = new Customer(name, email);
        return repo.save(c).getId();
    }
}

7.4 把 Service 暴露为 MCP Server

java 复制代码
@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}

// 只要 @Tool 注解的方法在 Spring 容器里,
// spring-ai-starter-mcp-server 会自动注册它们

7.5 完整 Agent 路由

java 复制代码
@RestController
public class AgentController {
    private final ChatClient chatClient;

    public AgentController(ChatClient.Builder b,
                          List<ToolCallback> localTools,    // 注解 @Tool
                          ToolCallbackProvider mcpTools) {  // MCP 服务器
        this.chatClient = b
            .defaultTools(mcpTools)        // MCP 工具
            .defaultToolCallbacks(localTools) // 本地工具
            .defaultSystem("你是一个 helpful 助手." +
                          "可以查客户信息,读写文件.")
            .build();
    }

    @PostMapping("/chat")
    public String chat(@RequestBody ChatRequest req) {
        return chatClient.prompt()
            .user(req.message())
            .call()
            .content();
    }
}

8. LangChain4j 集成:MCP 客户端

8.1 Maven 依赖

xml 复制代码
<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-mcp</artifactId>
  <version>1.0.0-beta1</version>
</dependency>

8.2 加载 MCP 工具

java 复制代码
import dev.langchain4j.mcp.McpToolProvider;
import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport;

import java.util.List;

public class AgentWithMcp {
    public static void main(String[] args) {
        // 启动 MCP server 子进程
        var transport = new StdioMcpTransport.Builder()
            .command(List.of("java", "-jar", "/path/to/mcp-fs-server.jar"))
            .environment(Map.of("MCP_FS_ROOT", "/data/workspace"))
            .build();

        // 把 MCP 工具转成 LangChain4j 的 ToolProvider
        var toolProvider = McpToolProvider.builder()
            .mcpServers(List.of(transport))
            .build();

        // 构建 LLM
        var model = OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .baseUrl("https://api.deepseek.com")
            .modelName("deepseek-chat")
            .build();

        // 构建 Agent
        var agent = AiServices.builder(Agent.class)
            .chatModel(model)
            .toolProvider(toolProvider)
            .build();

        String response = agent.chat("列出 /data/workspace 下的所有文件");
        System.out.println(response);
    }

    interface Agent {
        String chat(String message);
    }
}

8.3 4 种 Transport 对应 API

Transport Java 类 说明
stdio StdioMcpTransport 本地子进程
HTTP/SSE(旧) HttpMcpTransport 已废弃
Streamable HTTP StreamableHttpMcpTransport 新标准
WebSocket WebSocketMcpTransport LangChain4j 特有扩展

8.4 Docker stdio Transport

LangChain4j 特有,可以跑容器化的 MCP Server:

java 复制代码
var transport = new StdioMcpTransport.Builder()
    .command(List.of("docker", "run", "-i", "--rm",
                     "-v", "/data:/data",
                     "my-mcp-fs:latest"))
    .build();

9. Agent 设计模式

9.1 ReAct(Reason + Act)

最经典也最稳的模式:

复制代码
User: 深圳明天会下雨吗?

Iteration 1:
  Thought: 我需要查深圳明天的天气
  Action:  weather_lookup(city="深圳", day="tomorrow")
  Observation: {rain_prob: 75%, temp: 18-24}

Iteration 2:
  Thought: 75% 概率下雨,应该建议带伞
  Final Answer: 深圳明天 75% 概率下雨,18-24°C,建议带伞.

Spring AI 写法(默认就是 ReAct):

java 复制代码
ChatClient chat = chatClientBuilder
    .defaultTools(weatherTool, calendarTool)
    .build();

9.2 Plan-and-Execute

适合:复杂多步任务,需要先规划。

复制代码
User: 帮我调研竞品 A,B,C,写一份对比报告

Plan:
  Step 1: 搜索 A 的最近新闻
  Step 2: 搜索 B 的最近新闻
  Step 3: 搜索 C 的最近新闻
  Step 4: 总结成对比表
  Step 5: 写报告

Execute Step 1 -> ... -> Execute Step 5

Spring AI 1.0 写法 (用 agentic-patterns):

java 复制代码
// Spring AI 1.0 引入了 PlanningAgent
PlanningAgent agent = PlanningAgent.builder()
    .chatClient(chatClient)
    .planner(planner)
    .executor(executor)
    .build();

9.3 Reflection(自我反思)

适合:需要高质量输出的任务(写作,代码).

复制代码
Iteration 1:
  生成: "深圳适合创业..."

Reflection: "这段话太笼统,缺少数据支撑"

Iteration 2:
  生成: "根据 2025 报告,深圳..."

Spring AI 手工实现:

java 复制代码
public String reflectiveWrite(String topic) {
    String draft = chat("写一篇关于 " + topic + " 的短文");
    String critique = chat("请批判性评价以下短文,指出 3 个改进点:\n" + draft);
    String revision = chat("根据以下批评改进短文:\n" + critique);
    return revision;
}

9.4 Multi-Agent(多 Agent 协作)

CrewAI / AutoGen 风格:

复制代码
Researcher -> 找资料
  v
Writer -> 写文章
  v
Reviewer -> 审核
  v
Publisher -> 发布

Spring AI 1.0 实验特性 (@Agent 注解):

java 复制代码
@Agent
class WriterAgent {
    public String write(String topic) { ... }
}

@Agent
class ReviewerAgent {
    public String review(String text) { ... }
}

@Workflow
class PublishFlow {
    public String publish(String topic) {
        String draft = writerAgent.write(topic);
        String reviewed = reviewerAgent.review(draft);
        return reviewed;
    }
}

9.5 模式选型决策表

任务类型 推荐模式 原因
单步问答 简单 LLM 调用 ReAct 太重
简单工具调用 ReAct 经典稳定
复杂多步任务 Plan-and-Execute 可观测,可中断
写作/创作 Reflection 提高质量
角色扮演/团队 Multi-Agent 分工明确
高频实时数据 ReAct + 缓存 低延迟优先

10. Hermes Agent 实战

深入理解 SKILL 是怎么被加载的。

10.1 启动流程

复制代码
~/.hermes/config.yaml
    v
加载内置 Skills (~/.hermes/skills/built-in/)
    v
加载用户 Skills (~/.hermes/skills/)
    v
加载 MCP Servers (stdio/HTTP)
    v
合并工具集 (built-in + skills + mcp)
    v
Agent Ready

10.2 SKILL 加载机制

核心代码(Hermes gateway):

python 复制代码
def load_skill(skill_dir: Path) -> Skill:
    content = (skill_dir / "SKILL.md").read_text()
    frontmatter, body = parse_frontmatter(content)
    return Skill(
        name=frontmatter["name"],
        description=frontmatter["description"],
        body=body,
        path=skill_dir
    )

# 启动时:
all_skills = []
for d in Path("~/.hermes/skills").glob("*/SKILL.md"):
    all_skills.append(load_skill(d))

# 注入到 system prompt
system = build_system_prompt(all_skills)

关键点:

  • 所有 skill 的 name + description 都会进 system prompt
  • 模型看到用户问题后,自己决定调哪个 skill 的步骤
  • 这就是为什么 description 要写"Use when ..."

10.3 MCP 自动注册

python 复制代码
# ~/.hermes/config.yaml
mcp:
  servers:
    - name: github
      command: ["npx", "-y", "@modelcontextprotocol/server-github"]
      env: {GITHUB_TOKEN: "..."}

# 启动时:
for server in config.mcp.servers:
    client = McpClient.connect(server)
    tools = client.list_tools()  # 获取所有 tools
    for tool in tools:
        register_global_tool(tool)  # 注册到 agent

结果 :GitHub MCP 的 create_issue,list_repos 等工具直接出现在 Agent 工具列表,模型直接调用。

10.4 写一个生产级 SKILL

反面教材(description 太模糊):

yaml 复制代码
description: "Some useful tools"

-》模型不知道何时用

正面教材:

yaml 复制代码
name: code-review
description: "Use when the user asks to review, audit, or critique code. Triggers on phrases like 'review this code', 'check for bugs', 'is this secure', 'explain this function'. Don't use for: writing new code, formatting, or simple questions."

SKILL 大小限制(Hermes 实际约束):

  • description: <= 1024 字符
  • 全文: <= 100,000 字符
  • 实际建议 8-15K 字符(再大就拆 references/)

10.5 SKILL 的进阶组织

复制代码
~/.hermes/skills/
\--- production-incident/
    +--- SKILL.md              # 主入口
    +--- references/
    |   +--- runbook-mysql.md
    |   +--- runbook-redis.md
    |   \--- runbook-k8s.md
    +--- scripts/
    |   +--- collect_logs.sh
    |   \--- parse_stacktrace.py
    \--- templates/
        \--- incident-report.md

SKILL.md 里引用:

markdown 复制代码
## 步骤
1. 运行 `bash scripts/collect_logs.sh`
2. 查阅 [MySQL 故障手册](references/runbook-mysql.md)
3. 用 [事故报告模板](templates/incident-report.md) 输出

11. L2 小结

到这一步你已经能:

  • 读懂 MCP 协议的每个字段
  • 用 Java MCP SDK 写生产级 Server
  • 用 Spring AI Boot Starter 集成现有 Spring 系统
  • 用 LangChain4j 加载 MCP 工具
  • 根据任务选合适的 Agent 模式
  • 写一个真正好用的 SKILL

L3 我们进入商用:成本,性能,可观测性,安全,选型。

相关推荐
Hy行者勇哥1 小时前
2026 IT技术全景:算力超级周期下的三重重构与不可能三角,尤其需要关注“芯片自己设计自己”的情况
人工智能·重构
AC赳赳老秦1 小时前
OpenClaw + 云数据库运维:自动备份、扩容、迁移 RDS/MySQL 云数据库
运维·开发语言·数据库·人工智能·python·mysql·openclaw
Tbisnic1 小时前
AI大模型学习第十二天:Coze工作流与智能体开发
人工智能·python·ai·大模型·智能体·coze
冷小鱼1 小时前
高级研发编码习惯:从规范到艺术,再到AI+时代的人机协同
java·开发语言·python·编码习惯
m0_737302581 小时前
OpenClaw:从对话到执行,开启行动型 AI 智能体新时代
人工智能
齐 飞1 小时前
JDK21虚拟线程
java·后端
X54先生(人文科技)1 小时前
《元创力》纪实录·卷宗2.2署名权、龙标悖论与社会人格的剥夺
人工智能·开源·ai写作·零知识证明
小马爱打代码1 小时前
Java 并发 Bug 深度分析与实战
java
上海全爱科技1 小时前
80℃高温+多路4K解码实测报告-全爱科技后羿智盒QA500A2-B
人工智能