AI工具接入自定义MCP服务-串口
AI编程工具与MCP协议协同推动开发革新 摘要:人工智能技术正重塑软件开发模式,AI编程工具通过智能补全、自然语言转代码等功能显著提升效率。Anthropic推出的开源MCP协议(Model Context Protocol)通过动态上下文管理、多模态支持等特性优化大语言模型性能。本文演示了基于Spring Boot和SpringAI构建自定义MCP服务的过程,重点实现串口通信功能,包括端口管理、
前言
人工智能技术的快速发展正在深刻改变软件开发的方式。众多AI编程工具通过智能代码补全、自然语言转代码等功能,显著提升了开发效率。这些工具不仅能够理解开发者的意图,还能自动生成高质量的代码片段,甚至协助完成复杂业务逻辑的构建。随着AI技术的持续迭代,AI编程工具正从辅助角色向协作伙伴转变,开发者与AI的协同模式逐渐成为行业新常态。
MCP(Model Context Protocol)
是 Anthropic 设计的一种开源协议,主要用于优化大规模语言模型(LLM)的上下文管理。相关信息可以在这里查看。
- 核心功能
- 上下文窗口扩展:通过动态管理输入输出的上下文长度,提升模型处理长文本任务的效率,避免传统固定窗口的局限性。
- 多模态数据支持:支持文本、图像、结构化数据等多种形式的上下文输入,增强模型在复杂场景下的适应性。
- 资源优化:通过压缩和选择性缓存技术,降低长上下文场景下的计算和内存开销。
- 开源与标准化:提供统一的接口规范,便于开发者集成到不同框架中(如 transformers),促进生态协作。
- MCP 的发展趋势
- 长程依赖处理:未来可能进一步优化对超长文本(如书籍、代码库)的连贯性理解能力,结合稀疏注意力等技术减少计算成本。
- 实时交互增强:面向对话式 AI,探索低延迟的上下文更新机制,适用于客服、教育等实时场景。
- 跨模型兼容性:推动与其他协议(如 OpenAI 的 ChatML)的兼容,形成行业通用的上下文管理标准。
- 安全与隐私:引入端到端加密或差分隐私技术,确保敏感上下文数据(如医疗记录)的安全处理。
当前 MCP 仍处于早期阶段,但其开源特性与模块化设计为后续迭代提供了灵活性,可能成为 LLM 基础设施中的重要组件。将AI工具与自定义MCP服务相结合,本质上是通过软件定义硬件的能力突破。这种技术路径既能保留硬件稳定性优势,又能利用AI的动态优化能力,为传统嵌入式系统注入新的可能性。
简单来说就是自行编写自己需要的接口,实现通过AI对话调用,包括但不限于本地电脑的硬件端口操作(本文主要就以嵌入式开发比较常用的串口演示),目前又有非常多优秀的MCP服务可以在网上找到,比如魔搭社区。
自定义MCP服务
实现自定义MCP服务的方式有很多种,本文使用spring boot基于SpringAI来手把手带着各位看官实现:
勾选上Model Context Protocol Server和Spring Web:
重命名这个文件(个人习惯,不该改也行,只是格式有一点点区别)
在文件中添加必要配置参数,关键还是ai节点部分:
spring:
application:
name: serial_server
ai:
mcp:
server:
enabled: true
name: mcp_serial_server
version: 1.0.0
sse-endpoint: /mcp/serial/v1/sse # 自定义后续接入会用到
sse-message-endpoint: /mcp/serial/v1/mcp
type: SYNC
capabilities:
tool: true
server:
port: 8000
logging:
level:
io.modelcontextprotocol: TRACE
org.springframework.ai.mcp: TRACE
在pom.xml文件中添加必要依赖:
<!--串口开发依赖-->
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.10.4</version>
</dependency>
<!--其他工具类依赖 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.4.0-jre</version>
</dependency>
添加需要的jar包jSerialComm-2.10.4.jar
接下来就可以开始写代码了(为了缩减篇幅,删除了一些注释,尽量以最容易理解的方式实现),
新建SerialManager类主要内容如下:
import com.fazecast.jSerialComm.SerialPort;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class SerialManager {
public static SerialPort userSerial;
@Tool(description = "获取端口列表")
public static List<String> findPorts() {
// 获得当前所有可用串口
SerialPort[] serialPorts = SerialPort.getCommPorts();
List<String> portNameList = new ArrayList<>();
// 将可用串口名添加到List并返回该List
for (SerialPort serialPort : serialPorts) {
portNameList.add(serialPort.getSystemPortName());
}
portNameList = portNameList.stream().distinct().collect(Collectors.toList()); //去重
return portNameList;
}
@Tool(description = "打开端口")
public static SerialPort openPort(@ToolParam(required = true, description = "端口名字")String portName,
@ToolParam(required = true, description = "波特率")Integer baudRate) {
if(baudRate==0)
{
baudRate = 115200;
}
// 设置串口参数
userSerial = SerialPort.getCommPort(portName);
userSerial.setBaudRate(baudRate);
userSerial.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);
userSerial.setComPortParameters(baudRate, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
userSerial.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, 1000, 1000);
//开启串口
if (!userSerial.isOpen()) {
userSerial.openPort(1000);
} else {
return userSerial;
}
return userSerial;
}
@Tool(description = "关闭端口")
public static void closePort() {
if (userSerial != null && userSerial.isOpen()) {
userSerial.closePort();
}
}
@Tool(description = "发送字节数组")
public static void sendToPort(@ToolParam(required = true, description = "需要发送的数据缓存")byte[] content,
@ToolParam(required = true, description = "需要发送的字节数")Integer len) {
if (!userSerial.isOpen()) {
return;
}
userSerial.writeBytes(content, len);
}
@Tool(description = "读取字节数组")
public static byte[] readFromPort() {
byte[] reslutData = null;
if (!userSerial.isOpen()) {
return null;
}
if (userSerial.bytesAvailable() > 0) {
byte[] readBuffer = new byte[userSerial.bytesAvailable()];
int numRead = userSerial.readBytes(readBuffer, readBuffer.length);
if (numRead > 0) {
reslutData = readBuffer;
}
}
return reslutData;
}
}
代码中接口上面的@Tool(description = “”)是对应方法的描述,参数前面的
@ToolParam(required = true, description = “”)是对参数的描述,这些描述信息在被AI工具连接后作为接口提示存在,也是帮助AI联想到对应接口,应该尽量准确。
直接在SerialServerApplication把SerialManager注入到容器中:
@Bean
public ToolCallbackProvider serialManager(){
return MethodToolCallbackProvider.builder().toolObjects(new SerialManager()).build();
}
运行成功后在Console窗口中就可以看到启动的端口信息(application.yml中配置的)
先使用postman测试一下(如果没有这选项,选择最新版本注册并登录账号即可):
选择http方式
url输入http://localhost:8000/mcp/serial/v1/sse,端口后面这边部分就是application.yml文件中的sse-endpoint:配置。输入好后点击<Connect即可(有时候需要点击多次),连接成功后就会列出我们在SerialManager实现的方法:
选中对用方法,点击Run即可获取到返回值:
连接对应COM口,可以看到参数窗口上有参数对应的解释:
需要发送数据选中发送方法填入参数即可
这个看起来就熟悉了:
postman就测试到这里,接下来试试Cursor接入,打开软件,点击设置:
打开的json文件中填入如下信息(my-mcp-server: 在MCP Tools列表中显示的名字):
{
"mcpServers": {
"my-mcp-server": {
"type": "sse",
"url": "http://localhost:8000/mcp/serial/v1/sse"
}
}
}
编辑好保存后关闭文件,在Cursor Settings界面中就可以看到对应MCP服务:
点击右侧的开关(有时需要多点两次),连接成功后如下:
点击如下位置就可以看到暴露出来的接口:
鼠标停在接口名字上就可以看到接口介绍和参数简介:
现在试试看使用对话的方式能否调用这些接口:
打开一个对话框,输入“获取本地端口列表“,意思相近即可:
点击Run Tool后就会得到返回值:
我本地有COM1和COM2端口是连接上的,也就是这2个端口可以相互收发数据,AI这边连接COM1,并发送数据看串口助手里面能否接收到对应内容:
串口助手这边发送几个数据看AI这边能否接收到:
到此Cursor就测试完成了,如果需要接入网上其他MCP服务,方法基本类似。
再来看看VScode中集成的通义灵码能否接入,基本要求和添加步骤(通义灵码的安装网上教程还是比较多就不再重复了):
MCP广场选择中有一些MCP服务可以使用,这里我们直接手动添加刚才写的服务:
名称随意,类型选择SSE或HTTP,服务地址还是postman那个,编辑完后点击立即添加:
连接成功后就可以看熟悉的几个接口:
点击左上角智能会话,测试一下:
通过以上测试基本验证了AI工具与硬件接口之间实现交互。
更多推荐
所有评论(0)