
1. 从公众号文章采集痛点说起MCP 服务器到底是什么如果你做过内容聚合、竞品分析或者行业情报整理大概率遇到过这样的场景想批量拿到某个公众号的历史文章标题、摘要和正文手动复制粘贴效率极低写个爬虫脚本又得处理登录态、反爬、正文容器定位这些琐事。更麻烦的是采集完的数据还得手动喂给大模型做总结整个链路是断的。MCPModel Context Protocol模型上下文协议就是来解决这类AI 应用连接外部系统问题的开放标准你可以把它理解成 AI 应用的 USB-C 接口——只要你的工具按 MCP 规范暴露能力Claude、Cherry Studio 这类支持 MCP 的客户端就能直接调用不用为每个模型单独适配一遍。这篇文章聚焦 MCP 协议学习与 NodeJS 落地实践面向需要采集公众号文章信息的开发者。我会带你从零跑通一个可用的 MCP 服务器项目结构怎么搭、公众号文章抓取与解析怎么配、MCP 工具怎么注册、本地怎么启动和验证。核心检索词就三个MCP、NodeJS、爬取公众号文章信息。适合谁看有基础 NodeJS 经验、想理解 MCP 协议本质、并且希望手里有一个能真实调用工具的 MCP 服务器的开发者。读完你能得到一个可复制、可扩展的采集型 MCP 服务器骨架后续换成其他站点只需改解析逻辑。先说清楚 MCP 的架构不然后面写代码容易懵。MCP 遵循客户端-服务器架构由三部分组成MCP Host 是协调和管理一个或多个 MCP Server 的 AI 应用程序比如 Claude Desktop、Cherry StudioMCP Client 是 Host 内部为每个 Server 创建的连接组件负责从 Server 获取上下文MCP Server 就是你自己写的、为 Client 提供上下文数据源、工具、工作流的应用程序。三者是一对一连接关系Host 连几个 Server 就创建几个 Client。传输协议上本地进程间通信用 Stdio远程通信用 Streamable HTTP早期的 HTTP SSE 已经废弃原因是 SSE 只支持文本格式、跨平台兼容差、基于 HTTP/1.1 长连接性能受限而 Streamable HTTP 支持任意格式、可基于 HTTP/2/3 多路复用更适合 AI 场景里传 JSON、图片、音频这类混合数据。理解了这层你就知道为什么下面我们要用 StreamableHTTPServerTransport 而不是老的 SSE 方案。2. TaoToken 前置准备给 MCP 服务器接上模型能力MCP 服务器本身只负责提供工具真正决定要不要调用工具、怎么总结采集结果的是背后的大模型。所以在你写完采集工具之后需要一个能稳定调用模型的入口来验证整条链路。我这边用的是 TaoToken 提供的模型接入服务它兼容主流 API 调用方式配置成本低适合拿来跑通 MCP 的调用验证。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 注意 API 地址不带 UTM 参数配置时别写错。前置准备分三步。第一步拿到 API Key。进入控制台创建密钥路径是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 创建后复制保存后面配置客户端和验证请求都要用。第二步确认你要用的模型 ID。不同客户端对模型名的写法略有差异建议先在模型对话页面确认可用模型地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 选一个你熟悉的模型记下它的 Model ID。第三步如果你打算长期做编码类或 Agent 类任务可以了解下 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它更适合高频调用场景。这里要强调一个容易踩的坑MCP 服务器和模型接入是两件独立的事。MCP 服务器跑在本地 4000 端口负责暴露 crawlWeb 工具模型接入负责在客户端里配置 Base URL、API Key、Model ID 三件套。很多人第一次配的时候把两者混在一起结果工具能连上但模型不响应或者模型能对话但工具列表是空的。正确的顺序是先把 MCP 服务器本地跑起来确认 /mcp 端点能响应再在客户端里配置模型接入三件套最后在客户端里添加 MCP 服务器地址。这样出问题时能快速定位是工具层还是模型层的问题。如果你用的是 Claude Code 这类命令行工具接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有 Base URL 和鉴权头的完整写法照着填就行。3. 可复制配置NodeJS 项目结构与公众号采集工具注册这一节是全文技术核心给你一套可以直接复制的项目结构和配置。先建项目目录命令如下mkdir mcp-server-crawl cd mcp-server-crawl npm init -y npm install modelcontextprotocol/sdk zod3 express playwright npm install -D types/node types/express typescript ts-node mkdir src touch src/index.ts src/crawlWeb.ts然后修改 package.json重点是type: module启用 ES 模块加上 build 和 start 脚本{ name: mcp-server-crawl, version: 1.0.0, type: module, scripts: { build: tsc, start: node build/index.js }, dependencies: { modelcontextprotocol/sdk: ^1.20.1, express: ^5.1.0, playwright: ^1.56.1, zod: ^3.25.76 }, devDependencies: { types/express: ^5.0.3, types/node: ^24.9.1, ts-node: ^10.9.2, typescript: ^5.9.3 } }新增 tsconfig.json直接复制{ compilerOptions: { target: ES2022, module: Node16, moduleResolution: Node16, outDir: ./build, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true }, include: [src/**/*], exclude: [node_modules] }接下来写采集辅助函数。公众号文章的正文容器通常是#richTextContainer用 Playwright 无头浏览器打开页面后等待该选择器出现再取 textContent。这段逻辑放在 src/crawlWeb.tsimport { chromium } from playwright; export const crawlWebFn async (url: string) { const browser await chromium.launch({ headless: true }); const page await browser.newPage(); try { await page.goto(url, { waitUntil: domcontentloaded, timeout: 20000 }); await page.waitForSelector(#richTextContainer, { timeout: 10000 }); const content await page.$eval(#richTextContainer, (el) (el.textContent || ).trim() ); return content; } catch (error) { return error; } finally { await browser.close(); } };然后是主文件 src/index.ts创建 McpServer 实例、注册 crawlWeb 工具、用 Streamable HTTP 传输启动服务import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { StreamableHTTPServerTransport } from modelcontextprotocol/sdk/server/streamableHttp.js; import express from express; import { z } from zod; import { crawlWebFn } from ./crawlWeb.js; const server new McpServer({ name: mcp-server-crawl, version: 1.0.0, }); server.tool( crawlWeb, 爬取获取网页内容, { url: z.string().url().describe(需要被爬取的网页链接) }, async ({ url }) { try { const result (await crawlWebFn(url)) as string; return { content: [{ type: text, text: result }] }; } catch (error) { return { content: [{ type: text, text: 爬取网页失败 }] }; } } ); const app express(); app.use(express.json()); const transport new StreamableHTTPServerTransport({ sessionIdGenerator: () crypto.randomUUID(), }); await server.connect(transport); app.post(/mcp, async (req, res) { await transport.handleRequest(req, res, req.body); }); const port parseInt(process.env.PORT || 4000); app.listen(port, () { console.log(MCP Server running on http://localhost:${port}/mcp); });这里有个关键点server.tool的第三个参数是 zod schema它同时承担了参数校验和工具描述两个职责客户端拉取工具列表时会自动读取这份 schema不需要你额外写系统提示词。这就是 MCP 相比早期 Function Calling 的优势——工具描述标准化不用每家模型重新适配。如果你后面要加采集公众号文章列表这类工具照着 crawlWeb 的结构再注册一个即可参数里可以加account、pageSize这类字段。4. 验证请求本地启动与客户端调用成功结果配置写完先打包再启动npm run build npm run start终端输出MCP Server running on http://localhost:4000/mcp就说明服务起来了。这一步如果报错多半是 TypeScript 编译问题或 Playwright 浏览器没装先跑npx playwright install chromium补上浏览器内核。接下来在客户端验证。以 Cherry Studio 为例进入 MCP 服务器配置新建一个服务器类型选 Streamable HTTPURL 填http://localhost:4000/mcp名称和描述随意填保存后启动。启动成功后你应该能在工具列表里看到crawlWeb参数是url。如果工具列表是空的说明客户端没连上 /mcp 端点检查服务是否还在运行、端口是否被占用。然后发一条测试消息比如找一篇公众号文章链接让模型爬取这个文章并总结核心观点。正常情况下模型会先调用 crawlWeb 工具拿到正文文本再基于文本生成总结。整个过程你能在客户端的工具调用记录里看到完整的请求和返回。实测下来只要正文容器选择器对得上采集和总结这条链路是通的。如果模型没有调用工具而是直接回答通常是模型接入三件套没配对回到第 2 节检查 Base URL、API Key、Model ID。验证通过后你可以把采集结果进一步结构化。比如在 crawlWebFn 里除了 textContent再提取标题、作者、发布时间返回 JSON 字符串这样模型总结时能拿到更完整的元信息。公众号页面的标题一般在#activity-name作者在#js_name发布时间在#publish_time你可以按需扩展$eval逻辑。注意采集频率别太高单次请求之间加个几百毫秒延迟避免给目标站点造成压力。5. 本篇常见错排查401、local proxy failed 与 reading choices跑 MCP 服务器最容易卡在几个固定报错上我按出现频率排一下。第一个是401 Unauthorized这个几乎都出在模型接入层不是 MCP 层。原因通常是 API Key 写错、Key 过期、或者 Base URL 填成了带路径的地址。检查方法确认 Base URL 是https://taotoken.net/apiKey 从控制台重新复制一次注意别把前后空格带进去。如果用的是 Claude Code鉴权头格式要按接入文档写别自己拼。第二个是local proxy failed或连接被拒绝。这个报错说明客户端根本没连上你的 MCP 服务器。排查顺序先确认npm run start还在前台运行没退出再确认端口 4000 没被其他进程占用可以用lsof -i :4000查然后确认客户端里填的 URL 是http://localhost:4000/mcp而不是http://localhost:4000路径/mcp不能少。如果你在容器或远程环境跑localhost 要换成实际可达的地址。第三个是reading choices或返回内容为空。这个通常出在采集层Playwright 打开了页面但没等到#richTextContainer或者公众号文章改版后容器 ID 变了。解决办法把waitForSelector的超时调大或者先用有头模式headless: false打开看看页面实际结构确认正文容器的真实选择器。另外有些文章需要登录才能看全文这种情况采集到的内容会不完整属于预期内限制。第四个是 OAuth 相关报错。如果你接的是需要 OAuth 授权的远程 MCP 服务客户端会走授权流程报错多半是回调地址不匹配或 token 过期。本地 Stdio 或 Streamable HTTP 自建服务一般不涉及 OAuth遇到这类报错先确认你连的是不是自建服务。把上面四类报错对照排查一遍基本能覆盖 90% 的启动和调用问题。记住一个原则工具列表为空查 MCP 层模型不响应查接入层内容为空查采集层三层分开定位效率最高。6. 继续扩展把采集型 MCP 服务器用起来跑通基础版本后你可以按自己的采集需求扩展。比如加一个crawlArticleList工具输入公众号名称和页码返回文章标题和链接列表再加一个extractArticleMeta工具专门提取标题、作者、发布时间。工具注册的写法完全一致只是 zod schema 和内部逻辑不同。这样你的 MCP 服务器就从单篇采集升级成批量采集 结构化模型能调用的能力更完整。模型接入这边如果你要长期跑采集和总结任务建议用 Coding Plan 这类更适合高频调用的方案地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。API Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 可以按项目建不同的 Key 方便追踪用量。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到配置问题先翻文档再排查。模型对话页面 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 可以用来快速验证某个模型是否可用省得在客户端里反复试。最后给一个实用技巧把采集到的公众号文章正文先落盘成 markdown 文件再让模型读取本地文件做总结这样比每次重新爬取更稳定也方便你积累语料库。MCP 服务器负责采集本地文件系统负责存储模型负责分析三者解耦后维护成本低很多。等你熟悉了这套结构换成采集其他平台的内容也只是改解析选择器的事协议层和工具注册层几乎不用动。