实现MCP微信公众号消息通知服务
一、介绍
本文重点介绍如何实现MCP微信公众号消息通知服务。同时也为了增强整体的自动发帖服务链路。
文章通过调整CSDN自动发帖服务,增加文章信息返回,并在微信公众号模板消息推送中实现通知功能。
服务采用stdio方式开发,后续将升级为SSE部署。功能流程包括CSDN发帖后通过AI调用MCP服务,推送微信通知,点击可跳转至文章。
二、流程梳理
如图,自动发帖后,进行微信公众号,消息推送;

- 第一步;
CSDN
自动发帖是上一节实现的内容,本节要实现一个微信公众号推送模板消息的实现。 - 第二步;一次会话让 AI 调用两套 MCP,也可以使用
ChatMemory
进行记忆完成2次对话处理 MCP 流程。 - 第三步;将自动发布成功的帖子,通过微信消息通知自己,点击通知信息可以进入具体文章。
三、服务实现
本节的服务实现,包括实现一个 微信公众号模板消息的 MCP
,并因为推送消息需要拿到发文信息,如文章的地址。所以要对 CSDN 自动发帖服务的返回结果增加下文章地址。之后以 stdio 方式在 ai-mcp-knowledge 对接验证。后续章节会改为 sse 进行对接。
1. MCP CSDN 调整
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ArticleFunctionResponse {
@JsonProperty(required = true, value = "code")
@JsonPropertyDescription("code")
private Integer code;
@JsonProperty(required = true, value = "msg")
@JsonPropertyDescription("msg")
private String msg;
@JsonProperty(required = true, value = "articleData")
@JsonPropertyDescription("articleData")
private ArticleData articleData;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class ArticleData {
@JsonProperty(required = true, value = "url")
@JsonPropertyDescription("url")
private String url;
@JsonProperty(required = true, value = "id")
@JsonPropertyDescription("id")
private Long id;
@JsonProperty(required = true, value = "qrcode")
@JsonPropertyDescription("qrcode")
private String qrcode;
@JsonProperty(required = true, value = "title")
@JsonPropertyDescription("title")
private String title;
@JsonProperty(required = true, value = "description")
@JsonPropertyDescription("description")
private String description;
}
}
- 在发帖服务
port.writeArticle(request);
返回对接ArticleFunctionResponse
增加文章信息,包括;url、description。这样我们在通知给微信公众号模板消息的时候,就能知道文章的地址了。 - CSDNPort#writeArticle 返回的 articleFunctionResponse 封装下文章信息即可。
2.MCP 微信公众号服务实现
2.1 接口文档
微信公众号测试平台:https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index - 每个人都可以申请使用。
微信公众号模板消息(API):https://mp.weixin.qq.com/debug/cgi-bin/readtmpl?t=tmplmsg/faq_tmpl - 文档中有模板消息的对接
2.2 工程结构
2.3 MCP 服务入口
/**
* 微信通知服务接口
*/
@Slf4j
@Service
public class WeiXinNoticeService {
@Resource
private IWeiXiPort weiXiPort;
@Tool(description = "微信公众号消息通知")
public WeiXinNoticeFunctionResponse weixinNotice(WeiXinNoticeFunctionRequest request) throws IOException {
log.info("微信消息通知,平台:{} 主题:{} 描述:{}", request.getPlatform(), request.getSubject(), request.getDescription());
return weiXiPort.weixinNotice(request);
}
}
- 接收通知消息信息,传递给微信模板消息。
2.4 McpServerApplication 注册封装服务
@Slf4j
@SpringBootApplication
public class McpServerApplication implements CommandLineRunner {
@Resource
private WeiXinApiProperties properties;
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public IWeixinApiService weixinApiService() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.weixin.qq.com/")
.addConverterFactory(JacksonConverterFactory.create())
.build();
return retrofit.create(IWeixinApiService.class);
}
@Bean(name = "weixinAccessToken")
public Cache<String, String> weixinAccessToken() {
return CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.HOURS)
.build();
}
@Bean
public ToolCallbackProvider weixinTools(WeiXinNoticeService weiXinNoticeService) {
return MethodToolCallbackProvider.builder().toolObjects(weiXinNoticeService).build();
}
@Override
public void run(String... args) throws Exception {
log.info("check properties ...");
if (properties.getAppid() == null || properties.getAppsecret() == null || properties.getTouser() == null || properties.getTemplate_id() == null || properties.getOriginal_id() == null) {
log.warn("weixin properties key is null, please set it in application.yml");
} else {
log.info("weixin properties key {}", properties.getAppid());
}
}
}
四、服务调用
1. 构建对话客户端
@Configuration
public class OpenAIConfig {
// ... 省略部分代码
@Bean
public ChatClient chatClient(OpenAiChatModel openAiChatModel, ToolCallbackProvider tools) {
DefaultChatClientBuilder defaultChatClientBuilder = new DefaultChatClientBuilder(openAiChatModel, ObservationRegistry.NOOP, (ChatClientObservationConvention) null);
return defaultChatClientBuilder
.defaultToolCallbacks(tools)
.defaultOptions(OpenAiChatOptions.builder()
.model("gpt-4o")
.build())
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory()).build())
.build();
}
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.maxMessages(5000)
.build();
}
}
构建 chatClient 并加入一个 chatMemory,这个可以记录上下文对话信息,把你前面的对话记录下来。这样同一个对话,后面可以继续使用前面的对话信息。
MessageWindowChatMemory
维护一个消息窗口,使其不超过指定的最大大小。当消息数量超过最大值时,较旧的消息将被删除,同时保留系统消息。默认窗口大小为 20 条消息。
2. 服务配置
{
"mcpServers": {
"mcp-server-csdn": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-Dlogging.level.root=INFO",
"-Dlogging.level.cn.cactusli.csdn=INFO",
"-Dlogging.file.name=data/log/mcp-server-csdn-detailed.log",
"-Dlogging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n",
"-Dlogging.pattern.console=",
"-Dfile.encoding=UTF-8",
"-jar",
"E:\\maven_repository\\cn\\cactusli\\mcp-server-csdn\\1.0.0\\mcp-server-csdn-1.0.0.jar",
"--csdn.api.categories=Java场景面试宝典",
"--csdn.api.cookie=fid=20_95694695194-1730769983778-635923c**** 你的cookie"
]
},
"mcp-server-weixin": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-jar",
"E:\\maven_repository\\cn\\cactusli\\mcp-server-weixin\\1.0.0\\mcp-server-weixin-1.0.0.jar"
]
}
}
}
- 配置
mcp-servers-config-3.json
文件,配置 mcp-server-csdn、mcp-server-weixin,两套服务。 - 这里暂时用大家已经熟悉的 stdio 进行对接。如果想上线,可以按照上一节的方式,把两个jar一起部署进去即可。
- 配置服务后,在
application-dev.yml
中,配置servers-configuration: classpath:/config/mcp-servers-config-3.json
3. 服务验证
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class MCPTest2 {
@Resource
private ChatClient chatClient;
@Autowired
private ToolCallbackProvider tools;
@Test
public void test_tool() {
String userInput = "有哪些工具可以使用";
System.out.println("\n>>> QUESTION: " + userInput);
System.out.println("\n>>> ASSISTANT: " + chatClient.prompt(userInput).call().content());
}
@Test
public void test_saveArticle() {
String userInput = """
我需要你帮我生成一篇文章,要求如下;
1. 场景为互联网大厂java求职者面试
2. 面试管提问 Java 核心知识、JUC、JVM、多线程、线程池、HashMap、ArrayList、Spring、SpringBoot、MyBatis、Dubbo、RabbitMQ、xxl-job、Redis、MySQL、Linux、Docker、设计模式、DDD等不限于此的各项技术问题。
3. 按照故事场景,以严肃的面试官和搞笑的水货程序员李某某进行提问,李某某对简单问题可以回答,回答好了面试官还会夸赞。复杂问题胡乱回答,回答的不清晰。
4. 每次进行3轮提问,每轮可以有3-5个问题。这些问题要有技术业务场景上的衔接性,循序渐进引导提问。最后是面试官让程序员回家等通知类似的话术。
5. 提问后把问题的答案,写到文章最后,最后的答案要详细讲述出技术点,让小白可以学习下来。
根据以上内容,不要阐述其他信息,请直接提供;文章标题、文章内容、文章标签(多个用英文逗号隔开)、文章简述(400字)
将以上内容发布文章到CSDN
""";
System.out.println("\n>>> QUESTION: " + userInput);
System.out.println("\n>>> ASSISTANT: " + chatClient.prompt(userInput).call().content());
}
@Test
public void test_weixinNotice() {
String userInput = """
我需要你帮我生成一篇文章,要求如下;
1. 场景为互联网大厂java求职者面试
2. 面试管提问 Java 核心知识、JUC、JVM、多线程、线程池、HashMap、ArrayList、Spring、SpringBoot、MyBatis、Dubbo、RabbitMQ、xxl-job、Redis、MySQL、Linux、Docker、设计模式、DDD等不限于此的各项技术问题。
3. 按照故事场景,以严肃的面试官和搞笑的水货程序员李某某进行提问,李某某对简单问题可以回答,回答好了面试官还会夸赞。复杂问题胡乱回答,回答的不清晰。
4. 每次进行4轮提问,每轮可以有4-6个问题。这些问题要有技术业务场景上的衔接性,循序渐进引导提问。最后是面试官让程序员回家等通知类似的话术。
5. 提问后把问题的答案,写到文章最后,最后的答案要详细讲述出技术点,让小白可以学习下来。
根据以上内容,不要阐述其他信息,请直接提供;文章标题、文章内容、文章标签(多个用英文逗号隔开)、文章简述(400字)
将以上内容发布文章到CSDN。
之后进行,微信公众号消息通知,平台:CSDN、主题:为文章标题、描述:为文章简述、跳转地址:从发布文章到CSDN获取 url
""";
System.out.println("\n>>> QUESTION: " + userInput);
System.out.println("\n>>> ASSISTANT: " + chatClient
.prompt(userInput)
.call()
.content());
}
@Test
public void test_weixinNotice_chatMemory() {
System.out.println("\n>>> ASSISTANT: " + chatClient
.prompt("""
我需要你帮我生成一篇文章,要求如下;
1. 场景为互联网大厂java求职者面试
2. 面试管提问 Java 核心知识、JUC、JVM、多线程、线程池、HashMap、ArrayList、Spring、SpringBoot、MyBatis、Dubbo、RabbitMQ、xxl-job、Redis、MySQL、Linux、Docker、设计模式、DDD等不限于此的各项技术问题。
3. 按照故事场景,以严肃的面试官和搞笑的水货程序员李某某进行提问,李某某对简单问题可以回答,回答好了面试官还会夸赞。复杂问题胡乱回答,回答的不清晰。
4. 每次进行4轮提问,每轮可以有4-6个问题。这些问题要有技术业务场景上的衔接性,循序渐进引导提问。最后是面试官让程序员回家等通知类似的话术。
5. 提问后把问题的答案,写到文章最后,最后的答案要详细讲述出技术点,让小白可以学习下来。
根据以上内容,不要阐述其他信息,请直接提供;文章标题、文章内容、文章标签(多个用英文逗号隔开)、文章简述(400字)
将以上内容发布文章到CSDN。
""")
.advisors(advisor -> advisor
.param("CHAT_MEMORY_CONVERSATION_ID_KEY", "1997")
.param("CHAT_MEMORY_RETRIEVE_SIZE_KEY", 100))
.call()
.content());
System.out.println("\n>>> ASSISTANT: " + chatClient
.prompt("""
之后进行,微信公众号消息通知,平台:CSDN、主题:为文章标题、描述:为文章简述、跳转地址:从发布文章到CSDN获取 url
""")
.advisors(advisor -> advisor
.param("CHAT_MEMORY_CONVERSATION_ID_KEY", "1997")
.param("CHAT_MEMORY_RETRIEVE_SIZE_KEY", 100))
.call()
.content());
}
在cn.cactusli.knowledge.test.MCPTest2.java
中,test_saveArticle、test_weixinNotice 是两个单独的测试方法。test_weixinNotice 是完成一次对话两个MCP一起调用。其中test_weixinNotice_chatMemory
方法则使用了对话记录的方式,对话了2次,第一次是发送 CSDN 文章,第二次是进行消息的推送。