小红书多工具集成模式实战:如何连接 CLI/MCP/API 构建统一工作流
·
小红书多工具集成模式实战:如何连接 CLI/MCP/API 构建统一工作流
引言
在构建复杂的内容创作系统时,往往需要整合多个外部工具和服务。这些工具可能来自不同的技术栈——有些是命令行工具(CLI),有些是 Model Context Protocol(MCP)服务,有些则是 REST API。如何将这些异构工具统一集成到一条工作流中,是系统架构的核心挑战。本文通过实际案例,详细解析一种多工具集成的架构设计与实现方案。
一、工具分类与集成策略
1.1 工具类型谱系
系统涉及的工具按技术形态可分为三类:
┌─────────────────────────────────────────────────────────────┐
│ 工具类型谱系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ CLI │ │ MCP │ │ API │ │
│ │ 命令行工具 │ │ 协议服务 │ │ REST服务 │ │
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ │
│ │ xhs-cli │ │ xhs-mcp │ │ Jina Reader │ │
│ │ redbook │ │ douyin-mcp │ │ Exa Search │ │
│ │ xhs-cover │ │ weibo-mcp │ │ Twitter API │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 统一抽象接口设计
无论工具的技术形态如何,系统都将其抽象为统一的调用接口:
// 工具调用结果标准化
interface ToolResult<T = any> {
ok: boolean;
payload?: T;
blocker?: string; // 失败原因
}
// 工具元数据标准化
interface ToolMetadata {
id: string;
label: string;
status: "ready" | "partial" | "needs_auth" | "candidate";
detail: string;
command: string;
}
二、CLI 工具集成
2.1 子进程调用封装
CLI 工具通过 Node.js 的子进程机制调用,封装为通用的 runCommand 函数:
function runCommand(command, args, cwd, timeoutMs = 18000) {
return new Promise((resolve) => {
const child = spawn(command, args, {
cwd,
env: { ...process.env, FORCE_COLOR: "0" },
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let timedOut = false;
// 超时控制
const timer = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
}, timeoutMs);
child.stdout.on("data", (chunk) => {
stdout += chunk.toString();
});
child.stderr.on("data", (chunk) => {
stderr += chunk.toString();
});
child.on("close", (code) => {
clearTimeout(timer);
resolve({ code, stdout, stderr, timedOut });
});
});
}
2.2 CLI 工具调用模式
以小红书样本采集 CLI 为例:
export async function fetchNoteEvidenceWithXhsCli(noteUrl) {
const result = await runCommand(
"uv", // 命令
["run", "--python", "3.11", "xhs", "read", noteUrl, "--json"], // 参数
xhsCliRepoDir, // 工作目录
24000 // 超时 24秒
);
// 错误处理
if (result.timedOut) {
return { ok: false, blocker: "xhs-cli 读取超时" };
}
if (result.code !== 0) {
return { ok: false, blocker: extractError(result.stderr, result.stdout) };
}
// JSON 解析
const payload = safeJsonParse(result.stdout.trim());
if (!payload) {
return { ok: false, blocker: "xhs-cli 返回非 JSON" };
}
// 数据归一化
return { ok: true, payload: normalizeReadPayload(unwrapPayload(payload)) };
}
2.3 搜索功能集成
export async function searchNotesWithXhsCli(keyword, limit = 3) {
const result = await runCommand(
"uv",
["run", "--python", "3.11", "xhs", "search", keyword, "--json"],
xhsCliRepoDir,
24000
);
if (result.timedOut) {
return { ok: false, blocker: "xhs-cli 搜索超时" };
}
if (result.code !== 0) {
return { ok: false, blocker: extractError(result.stderr, result.stdout) || "搜索失败" };
}
const payload = safeJsonParse(result.stdout.trim());
if (!payload) {
return { ok: false, blocker: "xhs-cli 搜索返回非 JSON" };
}
// 搜索结果归一化
const normalized = normalizeSearchResults(unwrapPayload(payload), limit);
return { ok: true, payload: normalized };
}
function normalizeSearchResults(payload, limit = 3) {
const items = Array.isArray(payload?.items) ? payload.items : [];
return items
.map((item) => {
const noteCard = item.note_card || {};
const noteId = item.id || noteCard.note_id || "";
const xsecToken = item.xsec_token || noteCard.xsec_token || "";
return {
noteId,
title: noteCard.display_title || noteCard.title || "",
author: noteCard.user?.nickname || noteCard.user?.nick_name || "",
url: buildNoteWebUrl(noteId, xsecToken, "pc_search"),
metrics: normalizeMetrics(noteCard.interact_info || {}),
raw: item,
};
})
.filter(item => item.noteId && item.url)
.slice(0, limit);
}
三、MCP 服务集成
3.1 MCP 工具目录
系统维护了一个 MCP 工具目录,记录所有可用的 MCP 服务:
const officialPlatformMatrix = [
{
id: "web",
platform: "网页",
readyOutOfBox: "阅读任意网页",
localStatus: "ready",
localDetail: "Jina Reader 可直接用"
},
{
id: "youtube",
platform: "YouTube",
readyOutOfBox: "字幕提取 + 视频搜索",
localStatus: "ready",
localDetail: "yt-dlp 已安装"
},
{
id: "xiaohongshu",
platform: "小红书",
unlockAfterConfig: "阅读、搜索、发帖、评论、点赞",
localStatus: "needs_auth",
localDetail: "xhs-cli 已安装,需登录后验证"
},
{
id: "twitter",
platform: "Twitter/X",
readyOutOfBox: "读单条推文",
localStatus: "ready",
localDetail: "twitter-cli 已安装"
},
{
id: "bilibili",
platform: "B站",
readyOutOfBox: "本地:字幕提取 + 搜索",
localStatus: "ready",
localDetail: "bili + yt-dlp 已安装"
},
// ... 更多平台
];
3.2 MCP 层分类
MCP 服务按功能层次划分:
const acquisitionLayers = [
{
id: "exa",
layer1: "聚合搜索层",
layer2: "Exa",
status: "ready",
delivery: "结构化搜索结果 + highlights + publishedDate",
},
{
id: "jina-reader",
layer1: "网页抓取层",
layer2: "Jina Reader",
status: "ready",
delivery: "网页正文抽取",
},
{
id: "firecrawl",
layer1: "网页抓取层",
layer2: "Firecrawl",
status: "candidate",
delivery: "抓取、搜索、抽取、深度站点采集",
},
];
const contentLayers = [
{
id: "xiaohongshu",
layer1: "社媒平台源",
layer2: "小红书 / xhs-cli",
status: "needs_auth",
delivery: "搜索、阅读、评论、热点、feed",
},
{
id: "twitter",
layer1: "社媒平台源",
layer2: "Twitter / X",
status: "ready",
delivery: "搜索、时间线、帖子全文、长文",
},
{
id: "reddit",
layer1: "社区源",
layer2: "Reddit / rdt-cli",
status: "needs_auth",
delivery: "搜索、帖子、评论、subreddit",
},
];
const deliveryLayers = [
{
id: "markdown",
layer1: "文档交付",
layer2: "Markdown",
status: "ready",
delivery: "原始热点池 + 基础版 + 小红书版",
},
{
id: "feishu",
layer1: "协同交付",
layer2: "飞书",
status: "partial",
delivery: "飞书文档、表格、消息推送",
},
];
四、API 服务集成
4.1 HTTP API 调用模式
对于 REST API 服务,采用 fetch 或专用 HTTP 客户端:
// 假设的外部 API 集成示例
export async function fetchWithJinaReader(url) {
const readerUrl = `https://r.jina.ai/${encodeURIComponent(url)}`;
try {
const response = await fetch(readerUrl, {
headers: {
"Accept": "application/json",
},
});
if (!response.ok) {
return { ok: false, blocker: `HTTP ${response.status}` };
}
const contentType = response.headers.get("content-type");
if (contentType?.includes("application/json")) {
const data = await response.json();
return { ok: true, payload: data };
}
const text = await response.text();
return { ok: true, payload: { content: text } };
} catch (err) {
return { ok: false, blocker: err.message };
}
}
4.2 API 错误处理与重试
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const result = await fetch(url, options);
if (result.ok) return { ok: true, payload: await result.json() };
// 特定状态码处理
if (result.status === 429) {
await sleep(Math.pow(2, i) * 1000); // 指数退避
continue;
}
return { ok: false, blocker: `HTTP ${result.status}` };
} catch (err) {
if (i === retries - 1) {
return { ok: false, blocker: err.message };
}
await sleep(1000);
}
}
return { ok: false, blocker: "重试次数耗尽" };
}
五、工具链自动编排
5.1 多工具协同管道
当单一工具无法完成目标时,系统自动编排多个工具形成管道:
export async function enrichSourcesWithTools(sources) {
const xhsInstalled = await pathExists(path.join(xhsCliRepoDir, ".venv"));
const redbookReady = await pathExists(path.join(redbookRepoDir, "dist", "cli.js"));
const expanded = [];
for (const item of sources) {
// 搜索词类型 -> 先搜索再读取详情
if (item.type === "search_query") {
if (!xhsInstalled) {
expanded.push({ ...item, toolEvidence: null, blocker: "样本搜索器未就绪" });
continue;
}
const searchResult = await searchNotesWithXhsCli(item.raw, 3);
if (!searchResult.ok) {
expanded.push({ ...item, toolEvidence: null, blocker: searchResult.blocker });
continue;
}
// 将搜索结果展开为独立条目
searchResult.payload.forEach((result, index) => {
expanded.push({
id: `${item.id}-hit-${index + 1}`,
type: "note_url",
raw: result.title,
url: result.url,
manualNote: `来自搜索词:${item.raw}`,
derivedFrom: item.id,
});
});
continue;
}
// 主页链接类型 -> 标记阻塞(链路未通)
if (item.type === "home_url") {
expanded.push({
...item,
toolEvidence: null,
blocker: "主页链路未打通,需补 bridge / MCP / 发布器链路后再接。",
});
continue;
}
// 笔记链接 -> 读取 + 分析
if (item.type === "note_url") {
if (!xhsInstalled) {
expanded.push({ ...item, toolEvidence: null, blocker: "样本读取器未就绪" });
continue;
}
// Step 1: 读取笔记
const evidence = await fetchNoteEvidenceWithXhsCli(item.url);
if (!evidence.ok) {
expanded.push({ ...item, toolEvidence: null, blocker: evidence.blocker });
continue;
}
const payload = evidence.payload || {};
const body = payload.desc || payload.content || "";
const toolEvidence = {
title: payload.title || item.raw || "",
body,
author: payload.user?.nickname || payload.nickname || "",
noteId: payload.note_id || payload.id || "",
webUrl: item.url,
metrics: normalizeMetrics(payload.interact_info || {}),
hashtags: Array.isArray(payload.tag_list)
? payload.tag_list.map(tag => tag.name).filter(Boolean)
: [],
paragraphCount: paragraphCount(body),
preview: String(body || "").slice(0, 180),
analysis: null,
};
// Step 2: 爆款分析(可选)
if (redbookReady) {
const analysis = await analyzeNoteWithRedbook(item.url);
if (analysis.ok) {
toolEvidence.analysis = analysis.payload;
} else {
toolEvidence.analysisBlocker = analysis.blocker;
}
}
enriched.push({ ...item, toolEvidence });
}
}
// 去重
const deduped = [];
const seenNoteUrls = new Set();
for (const item of expanded) {
if (item.type === "note_url" && item.url) {
if (seenNoteUrls.has(item.url)) continue;
seenNoteUrls.add(item.url);
}
deduped.push(item);
}
return deduped;
}
5.2 工具状态检查
系统启动时检查所有工具的就绪状态:
export async function getToolStatusCatalog() {
const repos = await loadRepoCatalog();
const statusMap = {
"xiaohongshu-cli": {
id: "xiaohongshu-cli",
label: "样本采集器",
status: (await pathExists(path.join(xhsCliRepoDir, ".venv")))
? "可读样本"
: "待安装依赖",
detail: "本机已验证 status / search / read",
command: "cd repos/xhs-cli && uv run --python 3.11 xhs status --json",
},
redbook: {
id: "redbook",
label: "爆文拆解器",
status: (await pathExists(path.join(redbookRepoDir, "dist", "cli.js")))
? "可做拆解"
: "待构建",
detail: "本机已验证 analyze-viral / viral-template",
command: "cd repos/redbook && npm install && npm run build",
},
"xhs-cover-md": {
id: "xhs-cover-md",
label: "封面工坊",
status: (await pathExists(path.join(xhsCoverDir, "scripts", "capture.sh")))
? "可出图"
: "缺脚本",
detail: "本机已验证 HTML -> PNG",
command: "cd repos/xhs-cover-md && ./scripts/capture.sh input.html output.png",
},
// ... 更多工具
};
return repos.map((repo) => ({
...repo,
runtime: statusMap[repo.id] || {
id: repo.id,
label: repo.displayName || repo.name,
status: repo.status || "未配置",
detail: repo.role || "",
command: "",
},
}));
}
六、实战:构建完整工具链
6.1 爆款分析管道
完整的爆款分析需要多个工具协同:
export async function analyzeNoteWithRedbook(noteUrl) {
const result = await runCommand(
"node",
[
"dist/cli.js",
"analyze-viral",
noteUrl,
"--cookie-source", "chrome",
"--json"
],
redbookRepoDir,
30000 // 30秒超时
);
if (result.timedOut) {
return { ok: false, blocker: "redbook analyze-viral 超时" };
}
if (result.code !== 0) {
return { ok: false, blocker: extractError(result.stderr, result.stdout) };
}
const payload = safeJsonParse(result.stdout.trim());
if (!payload) {
return { ok: false, blocker: "redbook analyze-viral 返回非 JSON" };
}
// 数据归一化
return { ok: true, payload: normalizeViralAnalysis(payload) };
}
function normalizeViralAnalysis(payload) {
if (!payload || typeof payload !== "object") return null;
return {
note: payload.note || null,
score: payload.score || null,
hook: payload.hook || null,
content: payload.content || null,
engagement: payload.engagement || null,
comments: payload.comments || null,
relative: payload.relative || null,
fetchedAt: payload.fetchedAt || "",
summary: [
payload.hook?.title ? `标题=${payload.hook.title}` : "",
payload.engagement?.likes ? `点赞=${payload.engagement.likes}` : "",
payload.engagement?.comments ? `评论=${payload.engagement.comments}` : "",
payload.content?.paragraphCount ? `段落=${payload.content.paragraphCount}` : "",
].filter(Boolean).join(" · "),
};
}
6.2 模板提取管道
export async function buildViralTemplate(noteUrls) {
const urls = [...new Set((noteUrls || []).filter(Boolean))].slice(0, 3);
if (!urls.length) {
return { ok: false, blocker: "缺少样本笔记链接" };
}
const result = await runCommand(
"node",
[
"dist/cli.js",
"viral-template",
...urls,
"--cookie-source", "chrome",
"--json"
],
redbookRepoDir,
36000 // 36秒超时
);
if (result.timedOut) {
return { ok: false, blocker: "redbook viral-template 超时" };
}
if (result.code !== 0) {
return { ok: false, blocker: extractError(result.stderr, result.stdout) };
}
const payload = safeJsonParse(result.stdout.trim());
if (!payload) {
return { ok: false, blocker: "redbook viral-template 返回非 JSON" };
}
return { ok: true, payload: normalizeTemplateAnalysis(payload) };
}
function normalizeTemplateAnalysis(payload) {
if (!payload || typeof payload !== "object") return null;
return {
dominantHookPatterns: payload.dominantHookPatterns || [],
titleStructure: payload.titleStructure || null,
bodyStructure: payload.bodyStructure || null,
engagementProfile: payload.engagementProfile || null,
audienceSignals: payload.audienceSignals || null,
sourceNotes: payload.sourceNotes || [],
generatedAt: payload.generatedAt || "",
};
}
6.3 封面渲染管道
export async function renderCoverPng(htmlPath, outputPngPath) {
const result = await runCommand(
path.join(xhsCoverDir, "scripts", "capture.sh"),
[htmlPath, outputPngPath],
xhsCoverDir,
30000
);
if (result.timedOut) {
return { ok: false, blocker: "xhs-cover-md 出图超时" };
}
if (result.code !== 0) {
return { ok: false, blocker: extractError(result.stderr, result.stdout) };
}
return { ok: true, detail: result.stdout.trim() };
}
七、工具链监控与降级
7.1 统一错误处理
function extractError(stderr, stdout) {
const text = `${stderr || ""}\n${stdout || ""}`.trim();
return text.split("\n").filter(Boolean).slice(-3).join(" | ");
}
// 安全 JSON 解析
function safeJsonParse(text) {
try {
return JSON.parse(text);
} catch {
return null;
}
}
// 解包 payload(处理嵌套结构)
function unwrapPayload(payload) {
if (!payload || typeof payload !== "object") return payload;
if ("data" in payload && payload.data) return payload.data;
if ("result" in payload && payload.result) return payload.result;
return payload;
}
7.2 降级策略
async function buildInsights(brief, signals, sourceItems) {
const preset = SCENARIO_PRESETS[brief.scenarioId];
const context = fallbackContext(brief, signals);
const rankedEvidence = evidenceItems(sourceItems);
const topEvidence = rankedEvidence[0];
// 尝试 LLM 生成
try {
const { generateInsights: llmInsights } = await import("./llm.mjs");
const llmResult = await llmInsights({ /* ... */ });
if (llmResult?.mainHook) {
return { preset, context, insights: [ /* 转换结果 */ ] };
}
} catch (err) {
console.warn(`LLM buildInsights failed: ${err.message}`);
}
// 降级:使用本地规则
return {
preset,
context,
insights: [
{
label: "主钩子",
value: topEvidence?.analysis?.summary
|| "具体场景 + 明确受众 + 可执行动作",
},
// ...
],
};
}
八、架构优势总结
8.1 工具无关性
核心编排逻辑与具体工具解耦,新增或替换工具只需在适配层处理:
// 新增工具只需实现统一接口
export async function newToolAdapter(params) {
const result = await runCommand(/* ... */);
return { ok: true, payload: normalizeNewToolOutput(result) };
}
8.2 状态可视化
统一的工具目录系统提供了全局工具状态视图:
const toolCatalog = await getToolStatusCatalog();
// [
// { id: "xhs-cli", label: "样本采集器", status: "可读样本", ... },
// { id: "redbook", label: "爆文拆解器", status: "可做拆解", ... },
// ...
// ]
8.3 管道容错
单点故障不会导致整体流程中断:
for (const item of sources) {
try {
const result = await processWithTool(item);
results.push({ ...item, result });
} catch (err) {
// 单条失败不影响其他
results.push({ ...item, result: null, blocker: err.message });
}
}
结语
本文详细解析了多工具集成的架构设计与实现方案,涵盖 CLI、MCP、API 三种技术形态的集成模式,以及工具链编排、状态管理、错误处理和降级策略。这种设计使得系统能够灵活整合各种外部工具,构建高效的自动化工作流。
更多推荐



所有评论(0)