import assert from "node:assert/strict"; import test from "node:test"; import { collectMetricsFromMcp, collectXhsMetricsFromMcp, resolveAccountProfileFromMcp, resolveCollectionMcpConfig, resolveXhsPublicAccountDetails, resolveXhsPublicAccountId, resolveXhsAccountProfileFromMcp, resolveXhsProfileDetailsFromMcp, xhsNoteIdFromUrl, } from "../lib/mcp-collection-client.ts"; function sse(payload, options = {}) { return new Response( `event: message\ndata: ${JSON.stringify(payload)}\n\n`, { status: 200, ...options, headers: { "content-type": "text/event-stream", ...(options.headers ?? {}), }, }, ); } function toolEnvelope(payload, isError = false) { return { jsonrpc: "2.0", id: "tool-call", result: { isError, content: [ { type: "text", text: JSON.stringify(payload), }, ], }, }; } function createFakeMcp(toolResults, redirects = {}) { let toolIndex = 0; const calls = []; const fetchImpl = async (url, init) => { if (!init?.body) { const requestUrl = String(url); calls.push({ url: requestUrl, body: null, headers: new Headers(init?.headers) }); const location = redirects[requestUrl]; if (!location) throw new Error(`Unexpected public request: ${requestUrl}`); return new Response("", { status: 302, headers: { location } }); } const body = JSON.parse(init.body); calls.push({ url: String(url), body, headers: new Headers(init.headers) }); if (body.method === "initialize") { return sse( { jsonrpc: "2.0", id: body.id, result: { protocolVersion: "2025-03-26", capabilities: { tools: { listChanged: false } }, serverInfo: { name: "ai-tool-center", version: "test" }, }, }, { headers: { "mcp-session-id": "session-test" } }, ); } if (body.method === "notifications/initialized") { return new Response("", { status: 202 }); } if (body.method === "tools/call") { const result = toolResults[toolIndex]; toolIndex += 1; return sse(result); } throw new Error(`Unexpected MCP method: ${body.method}`); }; return { calls, fetchImpl }; } test("collects likes, comments and favorites from the verified MCP shape", async () => { const { calls, fetchImpl } = createFakeMcp([ toolEnvelope({ http_status: 200, response: { code: 200, success: true, msg: "获取内容详情成功", data: { likes: "483", comments: "41", collects: "519", }, }, }), ]); const result = await collectXhsMetricsFromMcp( "https://www.xiaohongshu.com/discovery/item/test?xsec_token=valid", { endpoint: "https://collector.example/mcp", key: "test-key", }, fetchImpl, ); assert.deepEqual(result, { likes: 483, comments: 41, collects: 519, shares: 0, }); assert.equal(calls.length, 3); assert.equal(calls[2].body.params.name, "fetch_content_detail"); assert.equal(calls[2].body.params.arguments.request.include_comments, false); assert.equal(calls[2].headers.get("mcp-session-id"), "session-test"); assert.equal(new URL(calls[0].url).searchParams.get("key"), "test-key"); }); test("collects through a stateless MCP server without a session header", async () => { const calls = []; const fetchImpl = async (_url, init) => { const body = JSON.parse(init.body); calls.push({ body, headers: new Headers(init.headers) }); if (body.method === "initialize") { return sse({ jsonrpc: "2.0", id: body.id, result: { protocolVersion: "2025-03-26", capabilities: { tools: { listChanged: false } }, serverInfo: { name: "ai-tool-center", version: "production" }, }, }); } if (body.method === "tools/call") { return sse( toolEnvelope({ response: { code: 200, success: true, data: { likes: 12, comments: 3, collects: 6 }, }, }), ); } throw new Error(`Unexpected MCP method: ${body.method}`); }; const result = await collectXhsMetricsFromMcp( "https://www.xiaohongshu.com/explore/test", { endpoint: "https://collector.example/mcp", key: "test-key" }, fetchImpl, ); assert.deepEqual(result, { likes: 12, comments: 3, collects: 6, shares: 0 }); assert.deepEqual( calls.map((call) => call.body.method), ["initialize", "tools/call"], ); assert.equal(calls[1].headers.get("mcp-session-id"), null); }); test("resolves the real XHS account profile from a submitted note link", async () => { const { calls, fetchImpl } = createFakeMcp([ toolEnvelope({ response: { code: 200, success: true, data: { note_detail: { products: [ { content_tags: [ { notes: [ { ip_location: "重庆", note_id: "6a671108000000000f004bef", user: { nickname: "N我的麻辣烫好了吗", profile_url: "https://www.xiaohongshu.com/user/profile/6905cbca0000000037009f49", red_id: "94329495984", user_id: "6905cbca0000000037009f49", }, }, ], }, ], }, ], }, }, }, }), toolEnvelope({ response: { code: 200, success: true, data: { fans_count: "734", }, }, }), ]); const profile = await resolveXhsAccountProfileFromMcp( "https://www.xiaohongshu.com/discovery/item/6a671108000000000f004bef", "回填昵称", { endpoint: "https://collector.example/mcp", key: "test-key", }, fetchImpl, ); assert.deepEqual(profile, { platformUid: "6905cbca0000000037009f49", nickname: "N我的麻辣烫好了吗", profileUrl: "https://www.xiaohongshu.com/user/profile/6905cbca0000000037009f49", redId: "94329495984", ipLocation: "重庆", followers: 734, gender: "", bio: "", recentNoteTitles: [], providerTags: [], }); const mcpCalls = calls.filter((call) => call.body?.method); assert.equal(mcpCalls.length, 4); assert.equal( mcpCalls[2].body.params.name, "fetch_content_detail", ); assert.equal( mcpCalls[2].body.params.arguments.request.link, "https://www.xiaohongshu.com/discovery/item/6a671108000000000f004bef", ); assert.equal(mcpCalls[3].body.params.name, "parse_xhs_user_summary"); assert.equal( mcpCalls[3].body.params.arguments.request.url, "https://www.xiaohongshu.com/user/profile/6905cbca0000000037009f49", ); assert.equal( xhsNoteIdFromUrl( "https://www.xiaohongshu.com/explore/6a671108000000000f004bef", ), "6a671108000000000f004bef", ); }); test("resolves followers directly from the supported XHS user summary tool", async () => { const { calls, fetchImpl } = createFakeMcp([ toolEnvelope({ response: { code: 200, success: true, msg: "用户摘要解析成功", data: { user: { fansCount: 6, ipLocation: "福建", nickname: "555 五", userId: "1020668113", gender: "女", desc: "分享城市周末与美食", tags: ["本地生活"], }, notes: [ { note_id: "note-1", title: "长沙湘菜探店", url: "https://www.xiaohongshu.com/explore/note-1", }, ], }, }, }), ]); const details = await resolveXhsProfileDetailsFromMcp( "https://www.xiaohongshu.com/user/profile/5fb21d32000000000101c23e", { endpoint: "https://collector.example/mcp", key: "test-key", }, fetchImpl, ); assert.deepEqual(details, { nickname: "555 五", followers: 6, redId: "1020668113", ipLocation: "福建", gender: "女", bio: "分享城市周末与美食", recentNoteTitles: ["长沙湘菜探店"], providerTags: ["本地生活"], }); assert.equal(calls[2].body.params.name, "parse_xhs_user_summary"); }); test("resolves an xhslink short URL before requesting the author profile", async () => { const fakeMcp = createFakeMcp([ toolEnvelope({ response: { code: 200, success: true, data: { note_detail: { note_id: "6a572da40000000021018bd2", ip_location: "上海", user: { nickname: "短链作者", user_id: "6905cbca0000000037009f49", }, }, }, }, }), toolEnvelope({ response: { code: 200, success: true, data: { red_id: "1020668113", followers: "1.2万", }, }, }), ]); const fetchImpl = async (url, init) => { if (String(url) === "http://xhslink.cn/o/AJFyP5dnj7O") { return new Response(null, { status: 302, headers: { location: "https://www.xiaohongshu.com/discovery/item/6a572da40000000021018bd2?xsec_token=valid", }, }); } return fakeMcp.fetchImpl(url, init); }; const profile = await resolveXhsAccountProfileFromMcp( "http://xhslink.cn/o/AJFyP5dnj7O", "回填昵称", { endpoint: "https://collector.example/mcp", key: "test-key", }, fetchImpl, ); assert.equal(profile.nickname, "短链作者"); assert.equal(profile.platformUid, "6905cbca0000000037009f49"); assert.equal(profile.redId, "1020668113"); assert.equal(profile.followers, 12_000); assert.equal( profile.profileUrl, "https://www.xiaohongshu.com/user/profile/6905cbca0000000037009f49", ); assert.equal( fakeMcp.calls[2].body.params.arguments.request.link, "http://xhslink.cn/o/AJFyP5dnj7O", ); }); test("uses the public note author when MCP profile lookup fails", async () => { const fakeMcp = createFakeMcp([ toolEnvelope( { response: { code: 500, success: false, msg: "作者详情暂时不可用", }, }, true, ), ]); const fetchImpl = async (url, init) => { if (String(url) === "http://xhslink.cn/o/AJFyP5dnj7O") { return new Response( '', { status: 200, headers: { "content-type": "text/html; charset=utf-8", location: "https://www.xiaohongshu.com/discovery/item/6a572da40000000021018bd2?xsec_token=valid", }, }, ); } if ( String(url) === "https://www.xiaohongshu.com/user/profile/5fb21d32000000000101c23e" ) { return new Response( '', { status: 200 }, ); } return fakeMcp.fetchImpl(url, init); }; const profile = await resolveXhsAccountProfileFromMcp( "http://xhslink.cn/o/AJFyP5dnj7O", "回填昵称", { endpoint: "https://collector.example/mcp", key: "test-key", }, fetchImpl, ); assert.deepEqual(profile, { platformUid: "5fb21d32000000000101c23e", nickname: "555 五", profileUrl: "https://www.xiaohongshu.com/user/profile/5fb21d32000000000101c23e", redId: "1020668113", ipLocation: "待识别", followers: 6, gender: "", bio: "", recentNoteTitles: [], providerTags: [], }); assert.equal( fakeMcp.calls[2].body.params.name, "fetch_content_detail", ); }); test("reads the user-visible Xiaohongshu number from a public profile", async () => { const fetchImpl = async () => new Response( '
小红书号:1020668113
', { status: 200 }, ); const accountId = await resolveXhsPublicAccountId( "https://www.xiaohongshu.com/user/profile/5fb21d32000000000101c23e", fetchImpl, ); const details = await resolveXhsPublicAccountDetails( "https://www.xiaohongshu.com/user/profile/5fb21d32000000000101c23e", fetchImpl, ); assert.equal(accountId, "1020668113"); assert.deepEqual(details, { nickname: "", redId: "1020668113", followers: 10, ipLocation: "", }); }); test("treats empty interaction counters from the current detail tool as zero", async () => { const { calls, fetchImpl } = createFakeMcp([ toolEnvelope({ response: { code: 200, success: true, data: { likes: "2", comments: "", collects: "", }, }, }), ]); const result = await collectXhsMetricsFromMcp( "https://www.xiaohongshu.com/explore/test", { endpoint: "https://collector.example/mcp?key=test-key", }, fetchImpl, ); assert.deepEqual(result, { likes: 2, comments: 0, collects: 0, shares: 0, }); assert.equal(calls.length, 3); assert.equal(calls[2].body.params.name, "fetch_content_detail"); }); test("surfaces current detail tool failures without calling removed tools", async () => { const { calls, fetchImpl } = createFakeMcp([ toolEnvelope( { response: { code: 400, success: false, msg: "获取内容详情失败", data: null, }, }, true, ), ]); await assert.rejects( collectXhsMetricsFromMcp( "https://www.xiaohongshu.com/explore/test", { endpoint: "https://collector.example/mcp?key=test-key", }, fetchImpl, ), /MCP工具 fetch_content_detail 返回失败(code 400):获取内容详情失败/, ); assert.equal(calls.length, 3); assert.equal(calls[2].body.params.name, "fetch_content_detail"); }); test("requires the MCP key without sending a network request", async () => { let requested = false; await assert.rejects( collectXhsMetricsFromMcp( "https://www.xiaohongshu.com/explore/test", resolveCollectionMcpConfig({}), async () => { requested = true; return new Response(); }, ), /MCP采集密钥未配置/, ); assert.equal(requested, false); }); test("rebuilds the MCP session after a gateway session miss", async () => { let initializeCount = 0; const fetchImpl = async (_url, init) => { const body = JSON.parse(init.body); if (body.method === "initialize") { initializeCount += 1; return sse( { jsonrpc: "2.0", id: body.id, result: { protocolVersion: "2025-03-26", capabilities: { tools: { listChanged: false } }, serverInfo: { name: "ai-tool-center", version: "test" }, }, }, { headers: { "mcp-session-id": `session-${initializeCount}`, }, }, ); } if ( body.method === "notifications/initialized" && initializeCount === 1 ) { return Response.json( { jsonrpc: "2.0", id: "server-error", error: { code: -32600, message: "Session not found" }, }, { status: 404 }, ); } if (body.method === "notifications/initialized") { return new Response("", { status: 202 }); } if (body.method === "tools/call") { return sse( toolEnvelope({ response: { code: 200, success: true, data: { likes: 8, comments: 2, collects: 5 }, }, }), ); } throw new Error(`Unexpected MCP method: ${body.method}`); }; const result = await collectXhsMetricsFromMcp( "https://www.xiaohongshu.com/explore/test", { endpoint: "https://collector.example/mcp", key: "test-key", }, fetchImpl, ); assert.deepEqual(result, { likes: 8, comments: 2, collects: 5, shares: 0 }); assert.equal(initializeCount, 2); }); test("collects Douyin likes, favorites, shares and comments", async () => { const { calls, fetchImpl } = createFakeMcp([ toolEnvelope({ response: { code: 200, success: true, data: { digg_count: "120", collect_count: "30", share_count: "8", comment_count: "12", }, }, }), ]); const result = await collectMetricsFromMcp( "https://www.douyin.com/video/7520000000000000000", "抖音", { endpoint: "https://collector.example/mcp", key: "test-key" }, fetchImpl, ); assert.deepEqual(result, { likes: 120, collects: 30, shares: 8, comments: 12, }); assert.equal(calls[2].body.params.name, "fetch_content_detail"); assert.equal(calls[2].body.params.arguments.request.plant, "dy"); }); test("maps the current Douyin MCP metric field names", async () => { const { fetchImpl } = createFakeMcp([ toolEnvelope({ response: { code: 200, success: true, data: { liked_count: "5682", collected_count: "565", share_count: "6878", comment_count: "332", }, }, }), ]); const result = await collectMetricsFromMcp( "https://v.douyin.com/5O5VpgomO2U/", "抖音", { endpoint: "https://collector.example/mcp", key: "test-key" }, fetchImpl, ); assert.deepEqual(result, { likes: 5682, collects: 565, shares: 6878, comments: 332, }); }); test("resolves a Douyin account from a submitted work link", async () => { const secUid = "MS4wLjABAAAA-test-profile-123456"; const { calls, fetchImpl } = createFakeMcp([ toolEnvelope({ response: { code: 200, success: true, data: { author: { nickname: "抖音作者", sec_uid: secUid, unique_id: "douyin-123", }, }, }, }), toolEnvelope({ response: { code: 200, success: true, data: { user: { nickname: "抖音作者", unique_id: "douyin-123", sec_uid: secUid, follower_count: "1.5万", ip_location: "上海", }, }, }, }), ]); const profile = await resolveAccountProfileFromMcp( "https://www.douyin.com/video/7520000000000000000", "抖音", "回填昵称", { endpoint: "https://collector.example/mcp", key: "test-key" }, fetchImpl, ); assert.deepEqual(profile, { platformUid: secUid, nickname: "抖音作者", profileUrl: `https://www.douyin.com/user/${secUid}`, redId: "douyin-123", ipLocation: "上海", followers: 15_000, gender: "", bio: "", recentNoteTitles: [], providerTags: [], }); assert.equal(calls[2].body.params.arguments.request.plant, "dy"); assert.equal(calls[3].body.params.name, "parse_dy_user_summary"); }); test("does not treat a Douyin short-link device id as the author sec_uid", async () => { const shortLink = "https://v.douyin.com/5O5VpgomO2U/"; const { calls, fetchImpl } = createFakeMcp( [ toolEnvelope({ response: { code: 200, success: true, data: { nickname: "搞怪噜噜😜", user_id: "4065311277723529", }, }, }), ], { [shortLink]: "https://www.iesdouyin.com/share/video/7671270545631842038/?did=MS4wLjABAAAA-device-token&with_sec_did=1", }, ); const profile = await resolveAccountProfileFromMcp( shortLink, "抖音", "回填昵称", { endpoint: "https://collector.example/mcp", key: "test-key" }, fetchImpl, ); assert.deepEqual(profile, { platformUid: "4065311277723529", nickname: "搞怪噜噜😜", profileUrl: "", redId: "", ipLocation: "待识别", followers: null, gender: "", bio: "", recentNoteTitles: [], providerTags: [], }); assert.equal(calls.find((call) => call.body === null)?.url, shortLink); assert.equal( calls.find((call) => call.body?.params?.name === "parse_dy_user_summary"), undefined, ); }); test("resolves a Douyin profile only when the public redirect exposes sec_uid", async () => { const shortLink = "https://v.douyin.com/author-sec-uid/"; const secUid = "MS4wLjABAAAAOqL4Jdu8htr7EWCDAyIr5z_7uvCAxhj-GOzCWg5zn8bDiKOp3WPw7lWkTyHvZpMY"; const { calls, fetchImpl } = createFakeMcp( [ toolEnvelope({ response: { code: 200, success: true, data: { nickname: "抖音作者", user_id: "4065311277723529", }, }, }), toolEnvelope({ response: { code: 200, success: true, data: { user: { nickname: "抖音作者", unique_id: "douyin-987", sec_uid: secUid, follower_count: "2.3万", ip_location: "广东", }, }, }, }), ], { [shortLink]: `https://www.iesdouyin.com/share/video/7671270545631842038/?sec_uid=${secUid}`, }, ); const profile = await resolveAccountProfileFromMcp( shortLink, "抖音", "回填昵称", { endpoint: "https://collector.example/mcp", key: "test-key" }, fetchImpl, ); assert.deepEqual(profile, { platformUid: secUid, nickname: "抖音作者", profileUrl: `https://www.douyin.com/user/${secUid}`, redId: "douyin-987", ipLocation: "广东", followers: 23_000, gender: "", bio: "", recentNoteTitles: [], providerTags: [], }); assert.equal( calls.find((call) => call.body?.params?.name === "parse_dy_user_summary") ?.body.params.arguments.request.url, `https://www.douyin.com/user/${secUid}`, ); });