基于mcp实现csdn自动发帖 (上)
简单的基于aimcp实现自动发帖,大家可以用当前小项目来做为mcp的练习
首先,我们应该知道mcp的实现逻辑,他是通过封装SyncMcpToolCallbackProvider的bean对象,告知ai我当前有什么工具,有什么作用,以及应该传入什么参数,然后让大模型来调用当前的对象
1.获取post请求的url参数
这里比较简单,我们新写一个文章的时候,会出现
将当前链接以url的格式copy之后,就知道有什么参数需要进行传入了,这边建议交给大模型来书写当前的post请求的参数
2.实体类的创建
首先,我们应该创建接受参数的实体类以及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;}}
Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ArticleFunctionRequest {@JsonProperty(required = true, value = "title")@JsonPropertyDescription("文章标题")private String title;@JsonProperty(required = true, value = "markdowncontent")@JsonPropertyDescription("文章内容")private String markdowncontent;@JsonProperty(required = true, value = "tags")@JsonPropertyDescription("文章标签,英文逗号隔开")private String tags;@JsonProperty(required = true, value = "Description")@JsonPropertyDescription("文章简述")private String Description;public String getContent() {return MarkdownConverter.convertToHtml(markdowncontent);}}
这这里我们加上jsonproperty的注解,实际上就是告知大模型,我们每一个的参数代表的是哪一个对象,这样大模型才能知道每一个对应的值需要传入的参数
3-对外方法封装
我们的实体类创建之后,实际上就是该封装方法了,在这里,我们可以先实现在本地可以跑起来的service,然后再将方法对象封装到toolcallbackprovider里面既可
public interface ICSDNService {@Headers({"accept: */*","accept-language: zh-CN,zh;q=0.9","content-type: application/json","dnt: 1","origin: https://editor.csdn.net","priority: u=1, i","referer: https://editor.csdn.net/","sec-ch-ua: \"Chromium\";v=\"134\", \"Not:A-Brand\";v=\"24\", \"Google Chrome\";v=\"134\"","sec-ch-ua-mobile: ?0","sec-ch-ua-platform: \"macOS\"","sec-fetch-dest: empty","sec-fetch-mode: cors","sec-fetch-site: same-site","user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36","x-ca-key: 203803574","x-ca-nonce: a70ca99e-8bfa-46d1-8d12-363c72707ebe","x-ca-signature: NGLzlIyvH7BuQgGJrgfGOzao0SVpzdTs4aTcw3hio6Y=","x-ca-signature-headers: x-ca-key,x-ca-nonce",})@POST("/blog-console-api/v3/mdeditor/saveArticle")Call<ArticleResponseDTO> saveArticle(@Body ArticleRequestDTO request, @Header("Cookie") String cookieValue);}
当前方法的注释实际上就是发送了请求,我们将一些固定不变的参数加入到headers注解里面即可
接下来就是发送对象的封装
@Slf4j
@Component
public class CSDNPort implements ICSDNPort {@Resourceprivate ICSDNService csdnService;@Resourceprivate CSDNApiProperties csdnApiProperties;@Overridepublic ArticleFunctionResponse writeArticle(ArticleFunctionRequest request) throws IOException {ArticleRequestDTO articleRequestDTO = new ArticleRequestDTO();articleRequestDTO.setTitle(request.getTitle());articleRequestDTO.setMarkdowncontent(request.getMarkdowncontent());articleRequestDTO.setContent(request.getContent());articleRequestDTO.setTags(request.getTags());articleRequestDTO.setDescription(request.getDescription());articleRequestDTO.setCategories(csdnApiProperties.getCategories());Call<ArticleResponseDTO> call = csdnService.saveArticle(articleRequestDTO, csdnApiProperties.getCookie());Response<ArticleResponseDTO> response = call.execute();log.info("请求CSDN发帖 \nreq:{} \nres:{}", JSON.toJSONString(articleRequestDTO), JSON.toJSONString(response));if (response.isSuccessful()) {ArticleResponseDTO articleResponseDTO = response.body();if (null == articleResponseDTO) return null;ArticleResponseDTO.ArticleData articleData = articleResponseDTO.getData();ArticleFunctionResponse articleFunctionResponse = new ArticleFunctionResponse();articleFunctionResponse.setCode(articleResponseDTO.getCode());articleFunctionResponse.setMsg(articleResponseDTO.getMsg());articleFunctionResponse.setArticleData(ArticleFunctionResponse.ArticleData.builder().url(articleData.getUrl()).id(articleData.getId()).qrcode(articleData.getQrcode()).title(articleData.getTitle()).description(articleData.getDescription()).build());return articleFunctionResponse;}return null;}}
4-启动类
启动类只需要做一件事情,将当前类作为方法暴露给其他想要调用的类
@SpringBootApplication
public class McpServerApplication implements CommandLineRunner {private final Logger log = LoggerFactory.getLogger(McpServerApplication.class);@Resourceprivate CSDNApiProperties csdnApiProperties;public static void main(String[] args) {SpringApplication.run(McpServerApplication.class, args);}@Beanpublic ICSDNService csdnService() {Retrofit retrofit = new Retrofit.Builder().baseUrl("https://bizapi.csdn.net/").addConverterFactory(JacksonConverterFactory.create()).build();return retrofit.create(ICSDNService.class);}@Beanpublic ToolCallbackProvider csdnTools(CSDNArticleService csdnArticleService) {return MethodToolCallbackProvider.builder().toolObjects(csdnArticleService).build();}@Overridepublic void run(String... args) throws Exception {log.info("check csdn cookie ...");if (csdnApiProperties.getCookie() == null) {log.warn("csdn cookie key is null, please set it in application.yml");} else {log.info("csdn cookie key is {}", csdnApiProperties.getCookie());}}}
当前类封装完了,下一篇文章会讲解调用和自动发布
