夜航星
Lila's dream
Lila's dream
Published on 2025-03-26 / 5 Visits
0
0

MCP入门和实践

#AI

github MCP学习指南:https://github.com/yzfly/Awesome-MCP-ZH

MCP 能干什么?

MCP 能让 AI 从“嘴炮王”变成“实干家”,以下是几个例子:

  1. 连工具:用 Slack 发消息、用 GitHub 管代码、用 Blender 建 3D 模型。

  2. 查数据:直接看你电脑文件、数据库记录,甚至网上实时信息。

  3. 干复杂活儿:写网页时,AI 能查代码、生成图片、调试页面,一条龙搞定。

  4. 人机协作:AI 干一半问你意见,你点头它再继续。

例子:在 Cursor 里装个 Slack MCP 服务器,AI 能一边写代码一边发消息通知团队,超省事!

MCP服务端支持功能清单:

功能

描述

浏览器自动化与网页交互

(让 AI 能够像人一样浏览网页、提取信息、填写表单等)

开发与代码执行

(让 AI 能够运行代码、分析代码库、与开发工具集成等)

命令行与 Shell 交互

(让 AI 能够执行命令行指令、与 Shell 交互)

版本控制 (Git / GitHub / GitLab)

(让 AI 能够操作代码仓库、管理 Pull Request、处理 Issues 等)

数据库交互

(让 AI 能够查询数据库、检查表结构、甚至修改数据)

云平台与服务集成 (AWS, Cloudflare, Azure, K8s, etc.)

(让 AI 能够管理云资源、调用云服务 API 等)

搜索

(让 AI 能够调用各种搜索引擎或专业搜索服务)

通讯与协作 (Slack, Email, Calendar, Social, etc.)

(让 AI 能够收发消息、管理日程、参与团队协作等)

金融与加密货币

(让 AI 能够获取金融数据、分析股票、与区块链交互等)

文件系统与存储

(让 AI 能够访问本地文件、操作云存储等)

数据分析、处理与可视化

(让 AI 能够处理表格数据、生成图表、进行数据探索等)

效率工具与集成 (Office, Project Management, Notes, etc.)

(让 AI 能够使用日历、任务管理、项目管理、笔记等工具)

multimedia 多媒体与内容创作

(让 AI 能够生成动画、编辑视频、处理图像、语音合成等)

知识、记忆与 RAG

(让 AI 拥有长期记忆、能够基于特定知识库回答问题等)

安全与分析

(让 AI 能够进行安全扫描、二进制分析、风险评估等)

地理位置与出行

(让 AI 能够处理地理位置数据、地图、天气、交通出行信息等)

体育与游戏

(让 AI 能够访问体育赛事数据、游戏信息等)

艺术与文化

(让 AI 能够访问艺术收藏、文化遗产、博物馆数据库等)

其他实用工具与集成

(包括计算器、API 集成、特定平台工具、聚合器、框架辅助等)

1、MCP客户端接入

什么是MCP:把 AI 想象成一个非常聪明的助手,但它只能在自己的房间里工作。MCP 服务集成就像给它安装了电话、网络和各种工具,让它能够:

  • 📞 调用外部服务(如地图、天气)

  • 💻 访问你的本地文件

  • 🔧 执行各种实用工具

MCP (Model Context Protocol) 让 AI 具备了与外部世界交互的能力。PIG AI 支持两种集成模式:

模式

图标

描述

SSE 模式

☁️ cloud

连接互联网上的各种服务 如:百度地图、天气查询等

本地模式

💻 computer

访问你电脑上的工具和文件 如:读写文件、运行命令等

1.SSE模式:连接云端服务

什么是 SSE 模式?

SSE (Server-Sent Events) 模式就像给 AI 接入了互联网。通过一个网址,AI 就能访问各种在线服务,比如:

  • 🗺️ 百度地图:查地址、规划路线

  • 🌤️ 天气服务:获取实时天气

  • 📊 数据服务:查询股票、汇率等

简单理解:你提供一个服务网址,AI 就能调用这个服务的所有功能!

image-20250925034610405

2.本地模式:让AI访问你的电脑

image-20250925034655580

什么是本地模式?

本地模式就像给 AI 装了一个"机器人手臂",让它能够:

  • 📁 读取和编辑你电脑上的文件

  • 🔍 搜索本地文档内容

  • ⚙️ 运行你电脑上的工具和命令

  • 📊 分析本地数据文件

本地 (STDIO) 模式通过标准输入/输出协议,让 AI 运行你电脑上的命令行工具。

环境工具介绍

Node.js 工具

  • 像一个"临时工具箱",需要什么工具就临时下载使用,用完就删除。

  • 适用场景:运行各种 Node.js 的 MCP 工具

  • 安装方式:

    1. 访问 Node.js 官网

    2. 下载并安装 Node.js (v18+)

    3. 安装完成后就可以使用 npx 命令了

Python 工具

  • 超快速的 Python 包管理器,比传统的 pip 更快更好用。

  • 适用场景:运行各种 Python 的 MCP 工具

  • 安装方式:参考 官方安装文档

3.实现原理和流程解析

1)新增MCP客户端服务

包含服务名称、描述、类型(SSE、STEAMABLE、STDIO),是否启用

  • SSE

    包含SSE地址、请求头参数配置

  • STDIO

    包含使用命令(uvx、npx、java、docker)、执行参数、环境变量

2)对话聊天

聊天实现流程同RAG知识库,只是在处理聊天请求时通过datasetId匹配MCP对应策略模式,采用的是McpChatRule规则

/**
 * 处理MCP聊天请求
 * <p>
 * 主要流程: 1. 如果用户未指定MCP,通过向量搜索自动选择 2. 获取MCP配置并创建或获取MCP客户端 3. 使用MCP工具提供者构建助手服务 4.使用模板处理用户输入并返回流式响应
 * @param chatMessageDTO 聊天上下文信息
 * @return AI响应结果流
 */
@Override
public Flux<AiMessageResultDTO> process(ChatMessageDTO chatMessageDTO) {
    List<Long> mcpIdList = new ArrayList<>();
    // 如果用户未提供MCP,则自动选择
    if (Objects.isNull(chatMessageDTO.getExtDetails())
            || StrUtil.isBlank(chatMessageDTO.getExtDetails().getMcpId())) {
        mcpIdList = autoChoice(chatMessageDTO);
        if (CollUtil.isEmpty(mcpIdList)) {
            return Flux.just(new AiMessageResultDTO("未找到相关MCP配置,请点击下方+按钮选择目标MCP"));
        }
    }
    else {
        // 如果用户提供了MCP ID,则直接使用
        mcpIdList.add(Long.parseLong(chatMessageDTO.getExtDetails().getMcpId()));
    }
​
    // 获取MCP配置
    List<AiMcpConfigEntity> mcpConfigEntityList = mcpConfigMapper
        .selectByIds(mcpIdList.stream().distinct().toList());
​
    // 通过McpClientProvider获取或创建MCP客户端
    List<McpClient> mcpClientList = mcpConfigEntityList.stream()
        .map(mcpClientProvider::getOrCreateMcpClient)
        .filter(Objects::nonNull)
        .toList();
​
    // 使用MCP客户端设置工具提供者
    ToolProvider toolProvider = McpToolProvider.builder().mcpClients(mcpClientList).build();
​
    // 根据请求的模型名称获取AI模型和助手服务
    Pair<StreamingChatModel, AiStreamAssistantService> servicePair = modelProvider
        .getAiStreamAssistant(chatMessageDTO.getModelName());
​
    // 使用工具提供者构建助手服务
    AiNoMemoryStreamAssistantService assistant = AiServices.builder(AiNoMemoryStreamAssistantService.class)
        .streamingChatModel(servicePair.getKey())
        .toolProvider(toolProvider)
        .build();
​
    // 使用模板处理聊天并返回响应流
    Map<String, Object> promptData = Map.of("mcpName",
            mcpClientList.stream().map(McpClient::key).collect(Collectors.joining(StrUtil.COMMA)), "inputMessage",
            chatMessageDTO.getContent(), "accessToken", SecurityUtils.getToken(), systemTime, DateUtil.now());
​
    TokenStream tokenStream = assistant.chatTokenStream(PromptBuilder.render("mcp.st", promptData));
​
    return Flux.create(fluxSink -> {
        tokenStream.beforeToolExecution(beforeToolExecution -> {
            AiMessageResultDTO.ToolInfo toolInfo = new AiMessageResultDTO.ToolInfo();
            toolInfo.setName(beforeToolExecution.request().name());
            toolInfo.setParams(beforeToolExecution.request().arguments());
            AiMessageResultDTO aiMessageResultDTO = new AiMessageResultDTO();
            aiMessageResultDTO.setToolInfo(toolInfo);
            fluxSink.next(aiMessageResultDTO);
        })
            .onPartialResponse(message -> fluxSink.next(new AiMessageResultDTO(message)))
            .onCompleteResponse(chatResponse -> fluxSink.complete())
            .onError(fluxSink::error)
            .start();
    });
}

创建mcp客户端实例(MCP 本质是一个 工具集协议:客户端通过它可以发现、调用远程工具):

/**
 * 创建MCP客户端实例
 * <p>
 * 根据配置的通信类型(SSE或STDIO)创建对应的传输器和客户端
 * @param mcpConfig MCP配置信息
 * @return MCP客户端实例
 */
private McpClient createMcpClient(AiMcpConfigEntity mcpConfig) {
    // 根据MCP类型创建适当的传输器
    McpTransport transport;
    if (McpTypeEnums.SSE.getType().equals(mcpConfig.getMcpType())) {
        transport = createHttpTransport(mcpConfig);
    }
    else if (McpTypeEnums.STEAMABLE.getType().equals(mcpConfig.getMcpType())) {
        transport = createStreamableTransport(mcpConfig);
    }
    else {
        transport = createStdioTransport(mcpConfig);
    }
​
    // 配置并构建MCP客户端
    try {
        return new DefaultMcpClient.Builder().transport(transport)
            .logHandler(new DefaultMcpLogMessageHandler())
            .build();
    }
    catch (Exception e) {
        log.error("创建MCP客户端失败: {}, 错误信息: {}", mcpConfig.getName(), e.getMessage(), e);
        return null;
    }
}

核心流程:

  1. 从数据库或配置中拿 MCP 配置。

  2. 通过封装类 McpClientProvider 创建/获取 McpClient,MCP 客户端是一个连接器,它知道如何和外部 MCP 服务交互

  3. McpToolProvider 把 MCP 客户端包装成 ToolProviderMcpToolProvider 会把多个 McpClient 聚合到一起,形成统一的 ToolProvider,告诉 LangChain4j:哪些工具可用,怎么调用

  4. 结合 LangChain4j 的 StreamingChatModel 创建 Assistant。LangChain4j 提供的 AiServices 机制,可以把模型和工具挂在一起,选定参数(StreamingChatModel使用的模型、toolProvider服务提供方、AssistantService无记忆聊天助手)

  5. 使用 Prompt 模板触发对话,流式处理响应。模板会告诉AI用户的输入、可用mcp工具、系统上下文信息。接着流式输出:

    tokenStream
        .beforeToolExecution(req -> { ... }) // 调用 MCP 工具前的事件(可记录日志、展示 UI)
        .onPartialResponse(msg -> { ... })  // 流式响应片段(边生成边输出)
        .onCompleteResponse(resp -> { ... })// AI 完成回答
        .onError(err -> { ... })            // 异常处理
        .start();

2、MCP服务端开发

image-20250925034837780

MCP服务端开发:将现有的业务接口包装成 MCP 服务,让 AI 能够调用你的接口,通过自然语言让 AI 帮你查询业务数据

掌握了这个方法,你可以将任何业务接口都包装成 MCP 服务: • 用户管理:让 AI 帮你查询用户信息、重置密码等 • 订单系统:通过自然语言查询订单状态、统计数据 • 库存管理:询问库存数量、预警信息等 • 财务报表:生成各种维度的财务分析报告 • 日志分析:智能检索和分析系统日志

1.业务接口自动转mcp原理

在 MCP 协议下,服务端的职责是:提供一组工具 (Tools),供客户端调用。 这些工具其实就是通过 @Tool 注解方法,MCP 框架会扫描并注册这些方法,让它们能被 AI/客户端动态发现并调用。

1)配置加载

@PropertySource(value = "classpath:mcp-config.yaml", factory = YamlPropertySourceFactory.class)
@Configuration(proxyBeanMethods = false)
@Import(ToolAutoRegister.class)
public class PigxAiMcpAutoConfiguration {
    @Bean
    public PigxSecurityToolAspect toolAspect() {
        return new PigxSecurityToolAspect();
    }
}
​
  • @PropertySource + YamlPropertySourceFactory 加载 mcp-config.yaml,用来配置 MCP 服务端相关参数(比如mcp服务端名称、版本号,同步/异步调用、描述、sse-endpoint主通道地址用来建立sse长连接,sse-message-endpoint消息推送的接口地址、能力声明、基础访问路径base-url)。

  • @Configuration(proxyBeanMethods = false) 声明配置类,不用代理方法(减少 Spring CGLIB 开销)。

  • @Import(ToolAutoRegister.class)工具自动注册逻辑ToolAutoRegister)导入 Spring 容器。

2)工具自动注册器

MCP工具自动配置类:自动扫描并注册带有@Tool注解的方法

public class ToolAutoRegister implements BeanPostProcessor, ApplicationContextAware {
    @Bean("controllerToolCallbackProvider")
    public ToolCallbackProvider controllerToolCallbackProvider() {
        List<Object> toolObjects = new ArrayList<>();
        String[] restBeanNames = applicationContext.getBeanNamesForAnnotation(RestController.class);
​
        for (String beanName : restBeanNames) {
            Object bean = applicationContext.getBean(beanName);
            Class<?> clazz = AopUtils.getTargetClass(bean);
            boolean match = Arrays.stream(clazz.getDeclaredMethods())
                .anyMatch(method -> method.isAnnotationPresent(Tool.class));
            if (match) {
                toolObjects.add(bean);
            }
        }
​
        return MethodToolCallbackProvider.builder().toolObjects(toolObjects.toArray()).build();
    }
}
  • 扫描所有 @RestController Bean。

  • 判断里面是否存在带 @Tool 注解的方法。

  • 如果有,就把整个 Bean 交给 MethodToolCallbackProvider

  • MethodToolCallbackProvider 会基于反射把这些方法注册成 MCP 工具(ToolCallback),暴露给客户端。

👉 这样,客户端通过 MCP 协议调用工具时,就能动态路由到这些方法

ApplicationContextAware:获取应用上下文,让一个 bean 拿到 Spring 容器的引用,这里是从容器里找出所有 Controller BeanPostProcessor:在 Spring 容器创建 bean 的生命周期中,提供两个“钩子”方法,让 bean 初始化前后做增强或替换,这里是在 bean 初始化阶段扫描 @Tool 注解,收集工具方法。最后注册成SpringAI的 ToolCallbackProvider → 作为提供者让 MCP 框架知道有哪些工具能对外暴露

3)权限切面拦截

@Slf4j
@Aspect
@RequiredArgsConstructor
public class PigxSecurityToolAspect implements Ordered {
    @SneakyThrows
    @Around("@annotation(tool)")
    public Object around(ProceedingJoinPoint pjp, Tool tool) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
​
        HasPermission annotation = method.getAnnotation(HasPermission.class);
        if (Objects.nonNull(annotation)) {
            McpContextHolder.setAnnotation(annotation);
        }
​
        try {
            return pjp.proceed(); // 执行工具方法
        } finally {
            McpContextHolder.clear();
        }
    }
​
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }
}
​
  • @Around("@annotation(tool)"):拦截所有带 @Tool 的方法。

  • 如果方法上还加了 @HasPermission,就存到 McpContextHolder(ThreadLocal 里)。

  • 执行完方法后清理上下文,避免线程污染。

👉 这样,调用 MCP 工具时可以自动做权限校验。

4)上下文管理器

@UtilityClass
public class McpContextHolder {
    private final ThreadLocal<HasPermission> THREAD_LOCAL = new TransmittableThreadLocal<>();
​
    public void setAnnotation(HasPermission annotation) {
        THREAD_LOCAL.set(annotation);
    }
​
    public HasPermission getAnnotation() {
        return THREAD_LOCAL.get();
    }
​
    public void clear() {
        THREAD_LOCAL.remove();
    }
}
​
  • ThreadLocal 保存每次工具调用时的权限注解(HasPermission)。

  • 结合 PigxSecurityToolAspect,保证在一个请求上下文内,工具能知道自己需要什么权限。

  • TransmittableThreadLocal,确保线程池环境下也能传递。


Comment