跳转至

🔌 MCP 是什么?它解决了什么问题?

MCP(Model Context Protocol,大模型上下文协议)是一套开放的标准化通信协议,专门用来让 LLM 应用与外部工具、数据源、资源进行安全、可发现的交互。

你可以把它想象成 “AI 世界的 USB-C”。

image.png

它解决的问题:工具与数据集的碎片化、集成成本高。

在 MCP 出现之前,每个 LLM 应用想要调用外部工具(数据库、文件系统、API)都需要:

  • 为每个工具写一套适配器(插件、函数调用封装)。

  • 不同应用之间无法复用这些集成。

  • 模型选择工具时缺乏统一描述和发现机制,导致“信息孤岛”。

MCP 做了一件事: 把工具提供者(Server)和 AI 应用(Host/Client)解耦。你写一个 MCP Server 暴露工具,所有支持 MCP 的 AI 应用都能直接发现并使用,就像你的 USB-C 充电器可以给手机、平板、笔记本充电一样。

image.png

⚙️ 核心工作原理、对比传统 API 的优势、适用场景

2.1 核心原理:以工具为中心的发现-调用模式

MCP 基于 JSON-RPC 2.0 协议,定义了三条核心原语:

  • Tools(工具):模型可调用的函数。Server 暴露一个工具列表,每个工具包含名称、描述、参数 JSON Schema。LLM 根据描述决定是否调用。

  • Resources(资源):可供 Client 读取的数据,比如文件内容、数据库记录、实时信息。类似 REST 的 GET,但通过统一协议暴露。

  • Prompts(提示模板):预定义的对话模板,帮助用户更好地与 LLM 互动。

调用流程:

image.png

2.2 与传统的 API 调用对比

查看内嵌表格

优势总结:

  • 互操作性:任何 MCP 客户端可以与任何 MCP 服务器对话。

  • 可发现性:LLM 能自己“看到”有哪些工具可用,从而自主决定调用哪一个。这是 Agent 自动化的基础。

  • 安全性:用户可以通过 Host 授权,控制 Client 能访问哪些 Server,并且支持人在回路(human-in-the-loop)审批。

  • 未来扩展:协议支持流式传输、资源订阅、通知,能适应复杂 Agent 场景。

2.3 适用场景

  • 构建 Agent 平台:你的 Agent 需要访问多个工具(日历、邮件、数据库)。用 MCP 让每个工具提供者只写一个 Server,平台统一接入。

  • IDE / 代码助手:你希望用户在自己的编辑器里直接操作数据库、调用云服务,集成 MCP Server 即可,无需为每个IDE重新开发插件。

  • 企业内部 LLM 网关:统一管理公司内的数据源和工具,AI 应用通过 MCP 安全、可控地访问它们。

  • 个人 AI 助理:你用 Claude Desktop,同时连接了文件系统 MCP Server、天气 MCP Server、笔记 MCP Server,模型可以自主查阅文件、获取天气并写入笔记。


🏗️ MCP 的三层架构(Host / Client / Server)是如何工作的?

MCP 采用分层架构,把用户交互、协议管理、后端执行彻底分开。

image.png

① Host(宿主应用)

  • 运行在用户侧的 AI 程序,如 Claude Desktop、VS Code 插件、自研 Web 界面。

  • 职责:

  • 解析 mcp_servers.json 配置文件,为每个 Server 启动一个子进程(stdio)或建立 HTTP 连接。
  • 持有多个 MCP Client 实例,管理它们的生命周期。
  • 将 LLM 的“工具调用意图”路由到对应的 Client,并将结果返回给 LLM。
  • 控制权限:用户在 Host 上看到“是否允许调用此工具?”的提示。

② Client(协议客户端)

  • 是 Host 内部的一个通信管理器,每个 Client 与一个 Server 一对一绑定。

  • 职责:

  • 维护与 Server 之间的传输通道(stdio、HTTP + SSE)。
  • 发送 JSON-RPC 请求:initializetools/listtools/callresources/read 等。
  • 接收并解析 Server 的响应,以及可能的推送通知。
  • 处理重连、错误、超时。

③ Server(工具提供者)

  • 一个实现了 MCP 协议的独立进程,通常由工具开发者编写。

  • 职责:

  • 声明自己的 capabilities(支持工具、资源、还是提示)。
  • 处理来自 Client 的请求:
    • tools/list:返回工具列表(名称、描述、参数 schema)。
    • tools/call:执行工具,返回结果。
    • resources/read:读取资源(如文件、数据库)。
  • 通常连接后端的真实数据源(数据库、REST API、文件系统)。

完整请求示例(Client → Server):

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "city": "Beijing"
    }
  }
}

Server 响应:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Beijing: 22°C, sunny."
      }
    ]
  }
}

💻 4. 代码示例:用 Python 实现一个天气 MCP Server

(使用 mcp 官方库,安装 pip install mcp

# weather_server.py
import asyncio
from mcp.server import Server, stdio_server
from mcp.types import Tool, TextContent

# 创建 Server 实例
server = Server("weather-server")

# 注册工具:列出可用的工具
@server.list_tools()
async def list_tools():
    return [
        Tool(
            name="get_weather",
            description="获取指定城市的当前天气",
            inputSchema={
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如 Beijing"
                    }
                },
                "required": ["city"]
            }
        )
    ]

# 实现工具调用
@server.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "get_weather":
        city = arguments["city"]
        # 模拟查询天气(实际可调 API)
        weather = f"{city}: 22°C, sunny."
        return [TextContent(type="text", text=weather)]
    raise ValueError(f"未知工具: {name}")

# 启动 Server(使用 stdio 传输)
async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, server.create_initialization_options())

if __name__ == "__main__":
    asyncio.run(main())

配置 Claude Desktop 使用这个 Server:

// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
{
  "mcp_servers": {
    "weather": {
      "command": "python",
      "args": ["path/to/weather_server.py"]
    }
  }
}

重启 Claude Desktop 后,问“北京天气怎么样?”,模型会自动发现 get_weather 工具,传入参数并得到天气回答。

🔌 MCP 的工具调用和普通 HTTP API 调用有什么本质区别?

表面上看,两者都是客户端发请求、服务端返回结果。但它们的设计哲学完全不同,这个差异决定了为什么 Agent 时代需要 MCP。

用一个对比图先看全貌:

image.png

四个本质区别:

查看内嵌表格

用代码感受一下差异:

调用一个天气查询,传统方式:

import requests
resp = requests.get(f"https://api.weather.com?city=Beijing&key=sk-xxx")
data = resp.json()  # 开发者自己写解析逻辑,然后拼进 prompt

MCP 方式(客户端侧):

# 1. 自动发现工具(不需要提前知道 URL 和参数名)
tools = mcp_client.list_tools()
# 返回: [Tool(name="get_weather", description="...", inputSchema={...})]

# 2. LLM 决定调用后,Client 自动序列化参数
result = mcp_client.call_tool("get_weather", {"city": "Beijing"})
# 返回结构化的 TextContent,可直接注入上下文

最大的区别在于:普通 API 是“给人看的”,MCP 是“给模型看的”。 后者提供了足够的语义信息,让 LLM 能够像人类阅读菜单一样,自主决定“我需要调用哪个工具、参数怎么填”。这是 Agent 能够进行自主规划和行动的基础。


⚙️ 2. 如何在 SpringAI 中接入 MCP Server?动态工具发现是如何实现的?

Spring AI 从 1.0.0-M5 开始提供了对 MCP 的原生支持。核心思路:把 MCP Server 提供的工具列表,动态转换成 Spring AI 的 Tool 定义,然后注入到 ChatClient 中,让模型可以自动调用。

架构示意图:

Spring AI 应用
├── ChatClient
│   └── ToolCallingAutoConfiguration
│       └── McpToolCallbackProvider ← 桥梁
│           └── McpClient ← 与 MCP Server 通信
│               └── stdio / SSE 传输
└── MCP Server (独立进程)
    └── 暴露 get_weather, query_db 等工具

接入步骤与代码示例:

第一步:引入依赖

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-mcp</artifactId>
    <version>1.0.0-M5</version>
</dependency>

第二步:配置 MCP Client 连接(以 stdio 方式为例)

@Configuration
public class McpConfig {

    @Bean
    public McpClient mcpClient() {
        // 通过子进程启动 Python 编写的 MCP Server
        ProcessBuilder pb = new ProcessBuilder("python", "/path/to/weather_server.py");
        return McpClient.using(pb).sync(); // 或 .async()
    }

    @Bean
    public ToolCallbackProvider weatherTools(McpClient mcpClient) {
        // 动态发现并注册所有工具
        return new McpToolCallbackProvider(mcpClient);
    }
}

第三步:在 ChatClient 中使用

@RestController
public class ChatController {

    @Autowired
    private ChatClient chatClient;

    @Autowired
    private ToolCallbackProvider weatherTools;

    @GetMapping("/ask")
    public String ask(@RequestParam String message) {
        return chatClient.prompt()
                .user(message)
                .tools(weatherTools.getToolCallbacks())  // 注入动态工具
                .call()
                .content();
    }
}

动态工具发现是如何实现的?

当你创建 McpToolCallbackProvider 时,它会:

  1. 调用 MCP Client 的 listTools() 方法,向 Server 发送 tools/list 请求。

  2. Server 返回一个工具列表,每个工具包含 namedescriptioninputSchema(JSON Schema)。

  3. Provider 遍历这个列表,把每个工具转换成 Spring AI 的 ToolCallback 对象——里面封装了工具名、参数定义,以及实际调用时通过 mcpClient.callTool() 执行的具体逻辑。

  4. 当模型决定调用某个工具时,Spring AI 会匹配对应的 ToolCallback,执行它,并将返回结果追加到对话上下文中。

整个过程对开发者完全透明,你不需要为每个工具单独写一个适配函数。 新增工具只需在 MCP Server 侧添加,Spring AI 应用重启后自动发现,这极大降低了工具集成的维护成本。


🧰 MCP Server 的三类能力(Resources/Tools/Prompts)有什么区别?

MCP 把 Server 能提供的能力抽象成了三个互补的元语,它们共同覆盖了“数据读取”、“行动执行”和“行为引导”三大需求。

image.png

① Resources(资源)

  • 本质:模型可读、但不可修改的数据端点。类似 REST 的 GET,但用统一协议暴露。

  • 特点:有唯一的 URI 标识(如 file:///documents/report.pdf),支持多种 MIME 类型。可以是被动读取,也支持通过 subscriptions 订阅变更通知。

  • 何时用:你的 Agent 需要访问文件系统、数据库视图、企业内部知识库等只读数据时。

② Tools(工具)

  • 本质:模型可以主动调用执行的函数,通常会产生副作用或返回计算结果。

  • 特点:包含完整的 JSON Schema 参数定义,使得 LLM 可以根据描述自主决定调用。执行结果返回为结构化内容(文本、图片等)。

  • 何时用:需要模型去“做某事”——发邮件、订机票、操作数据库、调用外部 API。

③ Prompts(提示模板)

  • 本质:预定义的对话模板,可以包含参数,帮助用户或模型快速启动特定类型的任务。

  • 特点:可以带参数(如 {topic}),并由 Server 提供默认值。一个 Server 可以暴露多个 Prompt,Host 能列出并让用户选择。

  • 何时用:你想要引导用户与 LLM 以特定方式交互。例如,一个“周报生成器”Server 可以提供“写周报”Prompt,用户只需填入项目名称,Prompt 自动组合出完整的指导语。

一个 Server 可以同时提供这三者。 比如一个“GitHub MCP Server”:

  • Resources:暴露仓库的文件树、README 内容(只读)。

  • Tools:提供 create_issuemerge_pr 等可执行操作。

  • Prompts:提供“生成 Commit Message”或“总结 Pull Request”的对话模板。

收尾:

这三类能力就像餐厅里的菜单(Prompts)、厨具(Tools) 和食材(Resources)。菜单告诉你能点什么,厨具帮你加工,食材是原材料。MCP 把它们标准化后,任何会“点菜”的 AI 都能在任意厨房里做出佳肴——这就是生态的力量。

🔷 MCP 和 Function Calling 的本质区别是什么?

一句话概括:

Function Calling 是模型能力边界的“扩展接口”,而 MCP 是连接模型与万物的“标准化总线”。

我们用一张图看清两者的定位差异:

image.png

五个维度的本质差异:

查看内嵌表格

用代码直观感受:

Function Calling 的典型流程(以 OpenAI 为例):

# 硬编码工具定义
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "获取天气",
        "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}
    }
}]
response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "北京天气"}],
    tools=tools
)
# 手动解析调用结果,再去请求真正的天气 API

MCP 的流程(模型无关):

# 1. 动态发现(不需要提前写死)
tools = mcp_client.list_tools()
# 2. LLM 看到工具列表后自主决策
decision = llm.think(user_input, available_tools=tools)
# 3. 统一执行
result = mcp_client.call_tool(decision.tool_name, decision.arguments)

本质总结:

Function Calling 是“厂家给你的遥控器”,只能控制厂家指定的几个电器。

MCP 是“万能遥控器 + 红外编码标准”,任何电器只要支持这个标准,就能被任何遥控器控制。

前者解决“能不能做”,后者解决“能不能一起做”。


🛡️MCP 工具调用的权限控制和安全设计如何做?

MCP 让模型能够自主调用外部工具,这带来了巨大的安全挑战。我们必须遵循纵深防御原则,在多个层级上设置权限检查点。

安全架构总览:

image.png

第一层:Host 层 — 身份认证与会话管理

  • 谁在操作? 必须在 Host 层面确认当前用户身份。对于 Web 应用,这通常是 JWT 或 OAuth2 令牌;对于桌面应用,可以是操作系统用户身份。

  • 安全实践:Host 初始化时要求用户登录,将用户 ID 或 Access Token 注入到 MCP Client 的上下文里,后续所有请求都携带该身份。

// Spring Security + JWT 获取当前用户
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String userId = auth.getName();
// 传递给 MCP Client
mcpClient.setUserContext(userId);

第二层:Client 层 — 工具权限过滤 & 人在回路

  • 哪些工具可用? 不是所有 Server 暴露的工具都应该对当前用户开放。Client 需要维护一份工具权限映射表。

  • 实现方式:在 ToolCallbackProvider 或 Client 的 listTools() 之后,根据用户角色过滤结果。

  • 人在回路(Human-in-the-loop):对高风险操作(如转账、删除数据),Host 应弹出确认对话框,让用户明确授权后才真正发送 tools/call

public List<Tool> getFilteredTools(McpClient client, String userId) {
    List<Tool> allTools = client.listTools();
    UserPermissions perms = permissionService.getPermissions(userId);
    return allTools.stream()
            .filter(tool -> perms.isAllowed(tool.getName()))
            .collect(Collectors.toList());
}

// 执行前检查
if (tool.isHighRisk() && !userConfirmed(tool.getName())) {
    throw new SecurityException("用户未授权高风险操作: " + tool.getName());
}

第三层:Server 层 — 参数校验 & 作用域控制

  • 输入必须被视为不可信。 即使 Client 做了初步过滤,Server 也必须对每次 tools/call 的参数进行严格校验。

  • 使用 JSON Schema 进行声明式校验(MCP 本身提供了 inputSchema,Server 应强制执行)。

  • 作用域与限流:Server 可以限制某个用户或 Client 的调用频率,防止滥用。

@PostMapping("/tools/call")
public ResponseEntity<CallToolResult> callTool(@RequestBody CallToolRequest request) {
    // 1. 校验工具是否存在
    Tool tool = toolRegistry.get(request.getName());
    if (tool == null) throw new ToolNotFoundException();

    // 2. 用 JSON Schema 校验参数
    SchemaValidator validator = new SchemaValidator(tool.getInputSchema());
    if (!validator.validate(request.getArguments())) {
        throw new InvalidArgumentsException();
    }

    // 3. 检查作用域 (例如,只能查询自己的数据)
    String userId = request.getMeta("user_id");
    if (!tool.isAllowedForUser(userId, request.getArguments())) {
        throw new ForbiddenException();
    }

    // 4. 执行并记录审计日志
    auditLog.record(userId, request.getName(), request.getArguments());
    return ResponseEntity.ok(tool.execute(request.getArguments()));
}

附加防线:

  • 传输层安全:生产环境中,Client-Server 之间使用 HTTPS + API Key 或 mTLS,绝不能在公网裸奔。

  • 内容安全:Server 返回的数据可能包含敏感信息,应在返回前做脱敏处理(如手机号中间四位打星)。

  • 审计追踪:每一次 tools/call 都要记录谁、什么时间、调用了什么工具、参数是什么,用于事后追溯和合规。

一句话记住: MCP 的威力在于“开放性”,而开放性的代价是“攻击面”。你在每一层都要问自己一个问题:“如果一个恶意 Prompt 要求调用这个工具,它能造成多大的破坏?” 答案越小,你的设计就越安全。


☕ 如何用 Java 实现一个 MCP Server?

我们基于 Spring Boot 和 Spring AI 的 MCP 支持,来实现一个可以提供“文件读取”和“系统信息”两个工具的 MCP Server。采用 WebFlux + SSE 传输模式,实现异步、流式通信。

步骤一:创建 Spring Boot 项目,引入依赖

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
    <version>1.0.0-M5</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

步骤二:配置 application.yml

spring:
  ai:
    mcp:
      server:
        name: "Utility MCP Server"
        version: "1.0.0"
        type: "SYNC" # 或 ASYNC
        tools:
          - name: read_file
            description: "读取本地文件内容"
          - name: system_info
            description: "获取当前系统信息"
server:
  port: 8081

步骤三:实现工具服务

import org.springframework.ai.mcp.server.McpServer;
import org.springframework.ai.mcp.server.McpServerProperties;
import org.springframework.ai.mcp.server.annotation.McpTool;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

@Component
public class UtilityTools {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @McpTool(name = "read_file", description = "读取指定路径的文本文件内容")
    public String readFile(String path) throws IOException {
        // 安全加固:只允许读取 /tmp 或特定沙箱目录
        Path filePath = Path.of(path).toAbsolutePath().normalize();
        if (!filePath.startsWith("/tmp/") && !filePath.startsWith("./data/")) {
            throw new SecurityException("不允许读取该路径: " + filePath);
        }
        return Files.readString(filePath);
    }

    @McpTool(name = "system_info", description = "获取当前服务器的操作系统、CPU和内存信息")
    public String systemInfo() throws IOException {
        Runtime rt = Runtime.getRuntime();
        Map<String, String> info = Map.of(
            "os.name", System.getProperty("os.name"),
            "os.arch", System.getProperty("os.arch"),
            "availableProcessors", String.valueOf(rt.availableProcessors()),
            "freeMemory", rt.freeMemory() / (1024*1024) + " MB",
            "totalMemory", rt.totalMemory() / (1024*1024) + " MB"
        );
        return objectMapper.writeValueAsString(info);
    }
}

步骤四:启动 Server MCP Server 会自动扫描 @McpTool 注解,生成工具列表并向连接上来的 Client 暴露。启动类无需特殊配置。

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

步骤五:与 Client 交互验证

启动 Server 后,可用 MCP 官方调试工具或一个简单的 Client 来测试。

// Client 端调用示例(伪代码)
McpClient client = McpClient.using("http://localhost:8081").sync();
List<Tool> tools = client.listTools(); // 自动发现 read_file, system_info
System.out.println(tools);

// LLM 决策后,Client 执行工具调用
String result = client.callTool("system_info", Map.of());
System.out.println(result); // 输出 CPU、内存等 JSON

关键设计考量:

  • 传输方式:示例选择了 WebFlux(SSE),适合需要流式更新(如长任务进度)的场景。如果是简单的请求-响应,可以用 spring-ai-starter-mcp-server-webmvc

  • 安全加固:文件读取工具中加入了路径白名单,避免路径遍历攻击。生产环境还需要进一步集成用户认证。

  • 工具发现:所有加了 @McpTool 的方法都会自动注册,无需额外配置,符合 MCP 的“动态发现”哲学。

收尾:

用 Java 实现 MCP Server 的核心,是让工具逻辑干净地聚焦在业务上,而注册、发现、传输、校验都由框架和协议兜底。你写的每一行代码,都不再是给某个应用打的专属补丁,而是在构建一块可以被任何 AI 复用的乐高积木。

🔌MCP Server 最核心的两个接口是什么?

如果从协议交互的“必须”和“高频”角度来选,MCP Server 最核心的两个接口是:

  • tools/list:让 Agent 知道你能做什么

  • tools/call:让 Agent 能够真的去做事

image.png

为什么是这两个?

  • tools/list 是动态发现的基础。没有它,Agent 就不知道有什么工具可用,只能靠人把工具描述硬编码在 prompt 里。MCP 最核心的创新之一就是让 LLM 自己看菜单点菜,而 tools/list 就是菜单本身。

  • tools/call 是价值落地的关键。Agent 通过 tools/list 知道了工具,但最终必须通过 tools/call 去执行数据库查询、文件读取、API 调用等具体操作。这是 MCP 从“描述”到“行动”的桥梁。

协议层面的典型消息:

请求 tools/list

{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}

响应:

{
  "tools": [
    {
      "name": "query_db",
      "description": "执行安全的 SQL 查询,只支持 SELECT",
      "inputSchema": {
        "type": "object",
        "properties": {
          "sql": {"type": "string", "description": "要执行的 SQL 语句"}
        },
        "required": ["sql"]
      }
    }
  ]
}

请求 tools/call

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "query_db",
    "arguments": {"sql": "SELECT name, stock FROM products WHERE stock < 10"}
  }
}

响应:

{
  "content": [{"type": "text", "text": "[{\"name\":\"Widget\",\"stock\":3},...]"}]
}

虽然 MCP 还定义了 resources/readprompts/get 等接口,但对于绝大多数 Agent 应用场景来说,tools/listtools/call 是让模型从“说”到“做”的最关键的两个。 理解这两个接口的职责和协作方式,就掌握了 MCP 的主动脉。


🐢开发了一个 MCP Server 提供数据库查询工具,上线后 Agent 调用响应很慢,如何排查优化?

现象: Agent 每次调用 query_db 都要等好几秒才返回,用户体验极差。我们需要系统性地定位瓶颈。

排查思路:分层定位法

image.png

第一层:工具内部埋点计时(定位是 Server 慢还是 DB 慢)

@McpTool(name = "query_db", description = "执行 SELECT 查询")
public String queryDb(String sql) {
    long start = System.currentTimeMillis();
    // 参数校验...
    long validEnd = System.currentTimeMillis();
    List<Map<String, Object>> result = jdbcTemplate.queryForList(sql);
    long queryEnd = System.currentTimeMillis();
    String json = objectMapper.writeValueAsString(result);
    long jsonEnd = System.currentTimeMillis();
    log.info("query_db 耗时: 校验={}ms, SQL={}ms, JSON序列化={}ms, 总={}ms",
             validEnd - start, queryEnd - validEnd, jsonEnd - queryEnd, jsonEnd - start);
    return json;
}

常见瓶颈及解决方案:

查看内嵌表格

第二层:连接池配置优化

// application.yml
spring.datasource.hikari:
  maximum-pool-size: 20        # 根据并发调高
  minimum-idle: 5
  connection-timeout: 3000     # 3
  idle-timeout: 600000
  max-lifetime: 1800000
  leak-detection-threshold: 10000  # 10秒未归还则告警

第三层:Agent 侧增加约束(从源头减少慢查询)

在工具描述中直接约定:

{
  "name": "query_db",
  "description": "执行 SQL 查询。注意:只支持 SELECT,必须包含 LIMIT 子句,最多返回 50 行。",
  "inputSchema": {
    "properties": {
      "sql": {
        "type": "string",
        "description": "SQL 查询语句,必须包含 LIMIT 且 LIMIT <= 50"
      }
    }
  }
}

并在 Server 端做强制检查:

if (!sql.toUpperCase().contains("LIMIT") || sql.contains("LIMIT 0")) {
    sql += " LIMIT 50";  // 强制追加,或直接返回错误提示
}

第四层:启用缓存

对相同的 SQL 查询,短时间内直接返回缓存:

@Cacheable(value = "queryCache", key = "#sql", unless = "#result.length() > 10000")
public String queryDb(String sql) { ... }

收束: 慢查询排查要像剥洋葱,一层层从内到外,先确定是代码、SQL、连接池还是网络。加上必要的防御性编程(强加 LIMIT),才能让 Agent 既灵活又不会打垮数据库。


🏢 如何设计一个支持多租户的 MCP Server?

多租户的核心要求是:一个 MCP Server 实例为多个客户服务,但数据严格隔离,工具行为可能因租户而异。

设计原则:

  1. 上下文传递:每次工具调用必须携带租户 ID。

  2. 数据隔离:数据库查询自动附加租户过滤,或动态切换数据源。

  3. 工具差异化:不同租户可能看到不同的工具集(如 VIP 租户开放高级分析工具)。

  4. 无状态 Server + 请求级隔离:不能把租户上下文存成全局变量。

架构图:

image.png

实现步骤(Spring Boot 示例):

第一步:定义租户上下文

public class TenantContext {
    private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();
    public static void set(String tenantId) { CURRENT_TENANT.set(tenantId); }
    public static String get() { return CURRENT_TENANT.get(); }
    public static void clear() { CURRENT_TENANT.remove(); }
}

第二步:用 Filter 从 MCP 请求头中提取租户 ID

在 MCP 的传输层(比如 HTTP SSE 或 stdio 无法带自定义头,所以通常在 HTTP 模式下使用 Header)。如果使用 Spring WebFlux,可以:

@Component
public class TenantFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        String tenantId = exchange.getRequest().getHeaders().getFirst("X-Tenant-Id");
        if (tenantId == null) {
            return Mono.error(new SecurityException("Missing X-Tenant-Id header"));
        }
        TenantContext.set(tenantId);
        return chain.filter(exchange).doFinally(signal -> TenantContext.clear());
    }
}

第三步:在工具方法中使用租户上下文

@McpTool(name = "query_customers", description = "查询当前租户的客户列表")
public String queryCustomers() {
    String tenantId = TenantContext.get();
    // 方案A:手动拼接租户过滤
    String sql = "SELECT * FROM customers WHERE tenant_id = '" + tenantId + "'";
    // 方案B:使用 MyBatis-Plus 多租户插件,自动拦截
    return jdbcTemplate.queryForList(sql).toString();
}

第四步:工具差异化(可选)

public List<Tool> getToolsForTenant(String tenantId) {
    List<Tool> tools = new ArrayList<>(baseTools);
    if ("VIP".equals(tenantService.getPlan(tenantId))) {
        tools.add(new Tool("advanced_analytics", "高级分析", ...));
    }
    return tools;
}

这样不同租户看到的 tools/list 返回结果不同。

多租户设计的核心就是一句话:让租户 ID 像一条隐形的水印,流淌在每一次工具调用的全链路中,数据层自动过滤,业务层按需定制。 永远不要在 Server 里缓存租户相关的全局状态,用 ThreadLocal 或请求上下文保持隔离,才能安全地为成百上千个客户服务。


🏭公司有一套内部系统(CRM、ERP、文档服务),需要让 Agent 能调用,如何设计接入方案?

这是一个典型的企业级 Agent 落地场景。目标是:用最小改造成本,把现有的内部系统能力暴露为 MCP 工具,供 Agent 统一调度。

核心方案:MCP Gateway + 适配器模式

不要修改现有系统,而是在它们前面架设一个 MCP 网关,由它负责协议翻译和调用编排。

image.png

设计要点:

  1. 统一认证层

MCP Gateway 负责对接 SSO,接收到 Agent 请求时,将用户身份映射为各个内部系统的访问令牌(可能不同系统使用不同凭证)。这种映射关系可以配置在网关中。

  1. 适配器抽象

为每个内部系统编写一个适配器,将系统的 API 翻译成 MCP Tool 定义。

public interface SystemAdapter {
    List<Tool> getTools();                          // 返回该系统提供的工具列表
    Object callTool(String toolName, Map<String, Object> args); // 执行调用
}

CRM 适配器实现示例:

@Component
public class CrmAdapter implements SystemAdapter {
    @Override
    public List<Tool> getTools() {
        return List.of(
            new Tool("search_customers", "搜索客户", Map.of("keyword", "string")),
            new Tool("get_customer_detail", "获取客户详情", Map.of("customerId", "string"))
        );
    }
    @Override
    public Object callTool(String toolName, Map<String, Object> args) {
        String token = ContextAuth.getToken("CRM");
        return switch (toolName) {
            case "search_customers" -> crmClient.search(args.get("keyword").toString(), token);
            case "get_customer_detail" -> crmClient.getById(args.get("customerId").toString(), token);
            default -> throw new ToolNotFoundException();
        };
    }
}
  1. 工具聚合与动态注册 MCP Server 启动时,扫描所有 SystemAdapter Bean,汇聚所有工具到一个统一列表。当 tools/list 被调用时,返回全公司所有可用工具。
@Bean
public List<Tool> aggregateTools(List<SystemAdapter> adapters) {
    return adapters.stream()
            .flatMap(adapter -> adapter.getTools().stream())
            .collect(Collectors.toList());
}
  1. 调用路由 收到 tools/call 时,根据工具名称找到对应的适配器,委托执行。
@McpTool(name = "*")  // 可以用动态路由
public Object routeCall(String toolName, Map<String, Object> args) {
    for (SystemAdapter adapter : adapters) {
        for (Tool tool : adapter.getTools()) {
            if (tool.getName().equals(toolName)) {
                return adapter.callTool(toolName, args);
            }
        }
    }
    throw new ToolNotFoundException();
}
  1. 容错与降级

引入 Resilience4j 进行熔断。当一个内部系统不可用时,及时切断,返回友好的错误提示,而不是让 Agent 无限等待。

@CircuitBreaker(name = "crm", fallbackMethod = "crmFallback")
public Object callCrmTool(String toolName, Map<String, Object> args) {
    // 调用逻辑
}
public Object crmFallback(Exception e) {
    return "CRM系统暂时不可用,请稍后重试。";
}

这个方案的价值在于: 内部系统的工程师不需要学 MCP,他们只需要维护自己的 API;而 AI 平台的工程师只需要写一次适配器,所有 Agent 就能立即“看懂”并使用整个公司的数字资产。这是一种非侵入式、可扩展、能渐进式建设的企业 AI 基础设施架构。