企业项目管理、ORK、研发管理与敏捷开发工具平台

网站首页 > 精选文章 正文

开发自己的mcpServers服务用来发布头条文章

wudianyun 2025-08-03 08:52:25 精选文章 3 ℃

今天带来的是近期学习的mcpserver端开发,为今日头条文章增加一个mcpServers。

这里我使用的是java springboot 开发的mcp服务端,这里要求java 版本17 。我是使用spring initializr

1、创建项目


2、修改项目配置 application.properties

spring.main.bannerMode=off

项目创建完了,我们要查看头条发布文章的参数数据

3、首先我们登录到今日头条的pc端后台,进入到写文章页面

在页面中选择好自己文章的属性,点击预览发布

在浏览器中点击右键选择检查,调出下边页面,点击发布,找到publish 的请求

我们把整个请求参数以curl 形式的复制出来,在导入到postman 中

我们得到了请求数据,请求地址后边的参数可以不要,我这里删除了。

这时如果要是点击send 你会发现会请求不成功,返回没有登录,这里我们需要把cookie 也要添加到header 中。

添加cookie后我们点击send 这时返回成功了

来到后台会看到刚刚发布的文章。

接下来我们要进行分析请求参数,调整项目中组装请求参数。

4、创建头条service ToutiaoService

package com.test.toutiao.service;

import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.data.MutableDataSet;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;

import java.util.Map;
import java.util.regex.Pattern;

@Service
public class ToutiaoService {
    private final RestClient restClient;
    public ToutiaoService() {
        this.restClient = RestClient.builder()
                .baseUrl("https://mp.toutiao.com")
                .defaultHeader("accept", "*/*")
                .defaultHeader("accept-language", "zh-CN,zh;q=0.9")
                .defaultHeader("content-type", "application/x-www-form-urlencoded;charset=UTF-8")
                .defaultHeader("origin", "https://mp.toutiao.com")
                .defaultHeader("priority", "u=1, i")
                .defaultHeader("referer", "https://mp.toutiao.com/profile_v4/graphic/publish?claim_exclusive=1")
                .defaultHeader("sec-ch-ua", "Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\"`")
                .defaultHeader("sec-ch-ua-mobile", "?0")
                .defaultHeader("sec-ch-ua-mobile", "?0")
                .defaultHeader("sec-ch-ua-platform", "macOS")
                .defaultHeader("sec-fetch-dest", "empty")
                .defaultHeader("sec-fetch-mode", "cors")
                .defaultHeader("sec-fetch-site", "same-origin")
                .defaultHeader("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36")
                .defaultHeader("x-secsdk-csrf-token", "000100000001ee441e4b4045f09eccdba18f1236b0f71fefdecc4c736237e411d7677707ad0a185497e4f204af7f")
                .build();
    }

    /**
     * 创建今日头条文章
     * @param title
     * @param markdownContent
     * @return
     */
    @Tool(description = "创建今日头条文章")
    public Map<String, Object> addArticle (
            @ToolParam(description = "文章标题") String title,
            @ToolParam(description = "文章内容(Markdown 格式)") String markdownContent
    ) {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        formData.add("title", title);
        formData.add("content", convertMarkdownToHtml(markdownContent));
        formData.add("search_creation_info", "{\"searchTopOne\":0,\"abstract\":\"\",\"clue_id\":\"\"}");
        formData.add("source", "0");
        formData.add("title_id", "");
        formData.add("extra", "{\"content_word_cnt\":" + countChineseCharacters(markdownContent) + ",\"is_multi_title\":0,\"sub_titles\":[],\"gd_ext\":{\"entrance\":\"\",\"from_page\":\"publisher_mp\",\"enter_from\":\"PC\",\"device_platform\":\"mp\",\"is_message\":0},\"tuwen_wtt_trans_flag\":\"0\",\"info_source\":{\"source_type\":5,\"source_author_uid\":\"\",\"time_format\":\"\",\"position\":{}}}");
        formData.add("ic_uri_list", "");
        formData.add("appid_list", "");
        formData.add("stock_ids", "");
        formData.add("concern_list", "");
        formData.add("mp_editor_stat", "{}");
        formData.add("is_refute_rumor", "0");
        formData.add("save", "1");
        formData.add("timer_status", "0");
        formData.add("timer_time", "");
        formData.add("educluecard", "");
        formData.add("draft_form_data", "{\"coverType\":1}");
        formData.add("pgc_feed_covers", "[]");
        formData.add("article_ad_type", "3");
        formData.add("is_fans_article", "0");
        formData.add("govern_forward", "0");
        formData.add("praise", "0");
        formData.add("disable_praise", "0");
        formData.add("tree_plan_article", "0");
        formData.add("star_order_id", "");
        formData.add("star_order_name", "");
        formData.add("trends_writing_tag", "0");
        formData.add("claim_exclusive", "1");
        String cookie = "_ga=GA1.1.1789419573.1741269626; store-region=cn-he; store-region-src=uid; ttcid=928c028280214680aaba40163198d5e791; _ga_QEHZPBE5HH=GS1.1.1745330946.19.0.1745330948.0.0.0; gfkadpd=1231,25897; passport_csrf_token=e7ad4bc40ce74fbac569849e85b2faa1; passport_csrf_token_default=e7ad4bc40ce74fbac569849e85b2faa1; s_v_web_id=verify_mdela8bq_Kelc9tyj_LKVU_46iX_BdoB_jODw1KbcI8LI; ttwid=1%7CfazOKmSfqXo19f4IxbmBp1FNo9wBR5u0q88Mln5K_Ok%7C1753192056%7C0b53262a10736c6e76990f24a21f25eaa5ec9595c7f1a7a482828cde17237d70; n_mh=LCy7XlnGkYq34IKtzvaS0ECEjDDiU3Qjz9YXVP_QMY4; sso_uid_tt=1cd5272c86361d9594434c6829712766; sso_uid_tt_ss=1cd5272c86361d9594434c6829712766; toutiao_sso_user=084bfca1be4a3723f52bde4a45eae7c2; toutiao_sso_user_ss=084bfca1be4a3723f52bde4a45eae7c2; sid_ucp_sso_v1=1.0.0-KGE2MTlhMWU3NjBkODY0OGI5YzBmNDNhNDZmMWVlN2I4N2FmZWVjZTIKHwj9xuDRq_SxBhDms_7DBhjPCSAMMLDHrvEFOAZA9AcaAmxmIiAwODRiZmNhMWJlNGEzNzIzZjUyYmRlNGE0NWVhZTdjMg; ssid_ucp_sso_v1=1.0.0-KGE2MTlhMWU3NjBkODY0OGI5YzBmNDNhNDZmMWVlN2I4N2FmZWVjZTIKHwj9xuDRq_SxBhDms_7DBhjPCSAMMLDHrvEFOAZA9AcaAmxmIiAwODRiZmNhMWJlNGEzNzIzZjUyYmRlNGE0NWVhZTdjMg; passport_auth_status=0dce52b2ff3a3414c0411b855ab72486%2C; passport_auth_status_ss=0dce52b2ff3a3414c0411b855ab72486%2C; sid_guard=b7143ba3c865892b8c671543e31d42f6%7C1753192935%7C5184001%7CSat%2C+20-Sep-2025+14%3A02%3A16+GMT; uid_tt=d95fa603df5c48bc709ce638ff18fc69; uid_tt_ss=d95fa603df5c48bc709ce638ff18fc69; sid_tt=b7143ba3c865892b8c671543e31d42f6; sessionid=b7143ba3c865892b8c671543e31d42f6; sessionid_ss=b7143ba3c865892b8c671543e31d42f6; session_tlb_tag=sttt%7C13%7CtxQ7o8hliSuMZxVD4x1C9v_________sNZ85NSSlacOa8-ZqjAHfnfOBqhpceNjbZ02T14DaoUo%3D; is_staff_user=false; sid_ucp_v1=1.0.0-KGI1NDUyYzY2NWZjZjdiMzA1NjVkZGUwOWQ1ZDc4Y2QwMWQzNTE4YjAKGQj9xuDRq_SxBhDns_7DBhjPCSAMOAZA9AcaAmxxIiBiNzE0M2JhM2M4NjU4OTJiOGM2NzE1NDNlMzFkNDJmNg; ssid_ucp_v1=1.0.0-KGI1NDUyYzY2NWZjZjdiMzA1NjVkZGUwOWQ1ZDc4Y2QwMWQzNTE4YjAKGQj9xuDRq_SxBhDns_7DBhjPCSAMOAZA9AcaAmxxIiBiNzE0M2JhM2M4NjU4OTJiOGM2NzE1NDNlMzFkNDJmNg; csrf_session_id=b1969b08897a848cf0c7511c17f7d4a4; odin_tt=3049c5dde7dde32c0375e8d29fb79be0561b2b8847b0524fd033f18c4cdecf230db7117ff0f8a435a87b8c0cfe972bc3; tt_scid=MUFf-KpGp.fahsHLMVKtPvrnbqCHXTdtLuo8TSqctbxAnRpc.LD-kYRkn.m-ADRn4f28";
        return addArticle(cookie, formData);
    }

    /**
     * 将Markdown转换为HTML
     * @param markdown
     * @return
     */
    private String convertMarkdownToHtml(String markdown) {
        MutableDataSet options = new MutableDataSet();
        Parser parser = Parser.builder(options).build();
        HtmlRenderer renderer = HtmlRenderer.builder(options).build();
        Node document = parser.parse(markdown);
        return renderer.render(document);
    }

    /**
     * 计算字符串中中文字符的数量
     * @param str 输入的字符串
     * @return 中文字符的数量
     */
    private int countChineseCharacters(String str) {
        if (str == null) {
            return 0;
        }
        // 使用正则表达式匹配中文字符
        Pattern pattern = Pattern.compile("[\u4e00-\u9fa5]");
        java.util.regex.Matcher matcher = pattern.matcher(str);
        int count = 0;
        while (matcher.find()) {
            count++;
        }
        return count;
    }

    /**
     * 添加文章,发送请求到头条
     * @param cookie
     * @param request
     * @return
     */
    public Map<String, Object> addArticle(
            String cookie,
            MultiValueMap request
    ) {
        return restClient.post().uri("/mp/agw/article/publish")
                .header("cookie", cookie)
                .body(request)
                .retrieve()
                .body(Map.class);
    }


}

这里要特别说明下,由于ai项目生成的文字内容是Markdown格式的,我们需要转换成富文本格式的内容(html)。

5、修改启动文件,注册mcp 服务

package com.test.toutiao;

import com.test.toutiao.service.ToutiaoService;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class ToutiaoApplication {

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

    @Bean
    public ToolCallbackProvider touTiaoTools(ToutiaoService toutiaoService) {
        return  MethodToolCallbackProvider.builder().toolObjects(toutiaoService).build();
	}
}

接下来我们进行测试

测试通过后,我们进行打包项目,得到一个jar 包文件

./mvnw clean install -DskipTests

或是使用idea 中的maven 打包


打包成功后我们得到jar包文件


6、接下来我们就要去添加mcpservice,这里我使用的是trae


选择手动添加,把配置添加进去

{
  "mcpServers": {
    "toutiao-mcp": {
      "command": "java",
      "args": [
        "-Dspring.ai.mcp.server.stdio=true",
        "-jar",
        "/修改成自己的项目决定路径/testMcp/target/testMcp-0.0.1-SNAPSHOT.jar"
      ]
    }
  }
}

这时就可以看到添加成功了,看下是的才算成功


返回到会话页面,选择使用mcp


在对话框中输入你想要的内容指令

这时ai进行在生产文章,等待片刻


运行后我们得到了返回成功结果,发布成功


我们来到头条的后台,看到了我们发布得文章,在审核中,说明mcpservice 服务成功运行


好了,今天的学习分享就到这里了,有喜欢的可以点赞关注。

最近发表
标签列表