#!/usr/bin/env node const path = require('path'); const { Client } = require('@modelcontextprotocol/sdk/client/index.js'); const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js'); const { version } = require('../package.json'); async function main() { const cwd = path.resolve(__dirname, '..'); const client = new Client({ name: 'voc-intelligence-smoke', version }); const transport = new StdioClientTransport({ command: process.execPath, args: ['mcp/src/server.js'], cwd, stderr: 'pipe' }); const stderr = transport.stderr; if (stderr) { stderr.on('data', chunk => { process.stderr.write(chunk); }); } await client.connect(transport); try { const tools = await client.listTools(); const toolNames = tools.tools.map(tool => tool.name); if (!toolNames.includes('fmode_image_analysis')) { throw new Error(`Expected tool fmode_image_analysis, got: ${toolNames.join(', ')}`); } const imageTool = tools.tools.find(tool => tool.name === 'fmode_image_analysis'); const imageSchema = JSON.stringify(imageTool?.inputSchema || {}); if (!imageSchema.includes('imagePath') || !imageSchema.includes('fmodeToken')) { throw new Error('Expected fmode_image_analysis to accept imagePath and fmodeToken inputs'); } const trendTool = tools.tools.find(tool => tool.name === 'voc_xiaohongshu_trend_run'); const trendSchema = JSON.stringify(trendTool?.inputSchema || {}); if (!trendTool || !trendSchema.includes('xiaohongshuToken') || !trendSchema.includes('vocToken')) { throw new Error('Expected voc_xiaohongshu_trend_run to accept simple request token inputs xiaohongshuToken and vocToken'); } if (!toolNames.includes('voc_xiaohongshu_trend_run')) { throw new Error(`Expected tool voc_xiaohongshu_trend_run, got: ${toolNames.join(', ')}`); } if (!toolNames.includes('voc_xiaohongshu_preference_update')) { throw new Error(`Expected tool voc_xiaohongshu_preference_update, got: ${toolNames.join(', ')}`); } if (!toolNames.includes('voc_problem_deep_dive_run')) { throw new Error(`Expected tool voc_problem_deep_dive_run, got: ${toolNames.join(', ')}`); } if (!toolNames.includes('voc_issue_pool_run')) { throw new Error(`Expected tool voc_issue_pool_run, got: ${toolNames.join(', ')}`); } if (!toolNames.includes('voc_content_plan_run')) { throw new Error(`Expected tool voc_content_plan_run, got: ${toolNames.join(', ')}`); } if (!toolNames.includes('voc_speaking_script_run')) { throw new Error(`Expected tool voc_speaking_script_run, got: ${toolNames.join(', ')}`); } if (!toolNames.includes('voc_competitor_map_run')) { throw new Error(`Expected tool voc_competitor_map_run, got: ${toolNames.join(', ')}`); } if (!toolNames.includes('voc_business_workflow_run')) { throw new Error(`Expected tool voc_business_workflow_run, got: ${toolNames.join(', ')}`); } const issuePoolTool = tools.tools.find(tool => tool.name === 'voc_issue_pool_run'); const issuePoolSchema = JSON.stringify(issuePoolTool?.inputSchema || {}); if (!issuePoolSchema.includes('evidence') || !issuePoolSchema.includes('resolvedIssues')) { throw new Error('Expected voc_issue_pool_run to expose evidence and status inputs'); } const deepDiveTool = tools.tools.find(tool => tool.name === 'voc_problem_deep_dive_run'); const deepDiveSchema = JSON.stringify(deepDiveTool?.inputSchema || {}); if (!deepDiveSchema.includes('project') || !deepDiveSchema.includes('feedback') || !deepDiveSchema.includes('blockedActions')) { throw new Error('Expected voc_problem_deep_dive_run to expose scoped memory and feedback inputs'); } const preferenceResult = await client.callTool({ name: 'voc_xiaohongshu_preference_update', arguments: { message: '保留奶油风和全屋定制翻车方向,不要纯风格美图,下一个版本更偏门店转化话术。', memory: '../outputs/claude-code-xhs-mcp-smoke/xiaohongshu-trend-memory.json' } }); const preferenceStructured = preferenceResult.structuredContent || {}; if (preferenceStructured.status !== 'ok') { throw new Error(`Expected ok preference result, got: ${JSON.stringify(preferenceStructured)}`); } const sentinelToken = 'SMOKE_REQUEST_TOKEN_VALUE'; const tokenCheckResult = await client.callTool({ name: 'voc_xiaohongshu_token_check', arguments: { vocToken: sentinelToken } }); const tokenStructured = tokenCheckResult.structuredContent || {}; const tokenText = JSON.stringify(tokenStructured); if (tokenStructured.status !== 'ok' || tokenStructured.configured !== true) { throw new Error(`Expected request-scoped token to be accepted, got: ${tokenText}`); } if (tokenText.includes(sentinelToken)) { throw new Error('Request-scoped token leaked in MCP token check result'); } const imageNoToken = await client.callTool({ name: 'fmode_image_analysis', arguments: { images: ['data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII='], prompt: '识别这张测试图片', allowEnvToken: false } }); if (!imageNoToken.structuredContent || imageNoToken.structuredContent.status !== 'needs_token') { throw new Error('image analysis no-token path returned unexpected structured content'); } if (Array.isArray(imageNoToken.structuredContent.errors) && imageNoToken.structuredContent.errors.length !== 0) { throw new Error('image analysis no-token path should keep errors empty'); } const result = await client.callTool({ name: 'voc_xiaohongshu_trend_run', arguments: { collectionMode: 'sample', profile: 'memory-templates/xiaohongshu-trend-profile.json', memory: '../outputs/claude-code-xhs-mcp-smoke/xiaohongshu-trend-memory.json', output: '../outputs/claude-code-xhs-mcp-smoke' } }); const structured = result.structuredContent || {}; if (structured.status !== 'ok') { throw new Error(`Expected ok result, got: ${JSON.stringify(structured)}`); } const assistantMessage = String(structured.assistantMessage || ''); const evidenceFields = ['## 证据样本', '原帖', '原文摘录', '高赞评论']; const missingEvidenceFields = evidenceFields.filter(field => !assistantMessage.includes(field)); if (missingEvidenceFields.length) { throw new Error(`Expected evidence card fields in sample report, missing: ${missingEvidenceFields.join(', ')}`); } if (/xiaohongshu\.com\/explore\//.test(assistantMessage)) { throw new Error('Sample report should not contain /explore/ Xiaohongshu links'); } const assetFiles = (structured.files || []).filter(file => /[\\/]assets[\\/]/.test(file)); if (!assetFiles.length) { throw new Error('Expected cached asset files in sample report result'); } const issuePoolResult = await client.callTool({ name: 'voc_issue_pool_run', arguments: { project: 'MCP示例品牌', industry: '家居定制', scenario: '线上获客', audience: '意向用户', evidence: [ '第一次买不知道怎么选,怕踩雷。', '价格有点高,不确定值不值。', '咨询的时候没人理。' ] } }); const issuePoolStructured = issuePoolResult.structuredContent || {}; if (issuePoolStructured.status !== 'ok' || !String(issuePoolStructured.assistantMessage || '').includes('## 问题优先级') || !issuePoolStructured.data?.issues?.length) { throw new Error(`Expected issue pool result, got: ${JSON.stringify(issuePoolStructured)}`); } const deepDiveResult = await client.callTool({ name: 'voc_problem_deep_dive_run', arguments: { issue: '怎么选', project: 'MCP示例品牌', industry: '家居定制', scenario: '线上获客', audience: '意向用户', evidence: ['第一次买不知道怎么选,怕踩雷。'], feedback: '老板不想做试用体验,更想先改评论区回复和案例展示' } }); const deepDiveStructured = deepDiveResult.structuredContent || {}; const deepDiveMessage = String(deepDiveStructured.assistantMessage || ''); if (deepDiveStructured.status !== 'ok' || !deepDiveMessage.includes('## 可执行解决动作') || !deepDiveMessage.includes('## 优先级判断') || !deepDiveStructured.summary?.memoryPath) { throw new Error(`Expected boss-oriented deep dive result, got: ${JSON.stringify(deepDiveStructured)}`); } if (!deepDiveStructured.data?.memory?.blockedActions?.includes('试用体验') || !deepDiveStructured.data?.memory?.preferredActions?.includes('评论区回复')) { throw new Error('Expected deep dive MCP call to parse feedback into scoped memory'); } if (!JSON.stringify(deepDiveStructured.nextActions || []).includes('live')) { throw new Error('Expected deep dive next actions to hand off to live collection instead of trapping the workflow'); } const contentPlanResult = await client.callTool({ name: 'voc_content_plan_run', arguments: { brand: 'MCP示例品牌', industry: '家居定制', issues: ['第一次买怕踩雷', '觉得价格贵', '怕质量不稳定'] } }); const contentPlanStructured = contentPlanResult.structuredContent || {}; const contentPlanMessage = String(contentPlanStructured.assistantMessage || ''); if (contentPlanStructured.status !== 'ok' || !contentPlanMessage.includes('7 天 VOC 内容选题') || !contentPlanMessage.includes('前 3 秒钩子') || !contentPlanStructured.data?.plan?.length) { throw new Error(`Expected VOC content plan result, got: ${JSON.stringify(contentPlanStructured)}`); } const speakingScriptResult = await client.callTool({ name: 'voc_speaking_script_run', arguments: { brand: 'MCP示例品牌', industry: '家居定制', topic: '第一次买怎么选不踩雷', userIssue: '第一次买怕选错', evidence: ['第一次买不知道怎么选,怕踩雷'], feedback: '开头更狠一点,老板视角,少讲概念,多给具体场景', memory: '../outputs/claude-code-xhs-mcp-smoke/voc-speaking-script-memory.json', finalize: true } }); const speakingScriptStructured = speakingScriptResult.structuredContent || {}; const speakingScriptMessage = String(speakingScriptStructured.assistantMessage || ''); if (speakingScriptStructured.status !== 'ok' || !speakingScriptMessage.includes('VOC 口播脚本共创') || !speakingScriptMessage.includes('60 秒口播稿') || !speakingScriptMessage.includes('30 秒压缩版') || !speakingScriptStructured.data?.memory?.finalizedScripts?.length || !speakingScriptStructured.data?.memory?.preferredStyles?.includes('老板视角')) { throw new Error(`Expected VOC speaking script result, got: ${JSON.stringify(speakingScriptStructured)}`); } const competitorMapResult = await client.callTool({ name: 'voc_competitor_map_run', arguments: { brand: 'MCP示例品牌', city: '本地', category: '家居定制' } }); const competitorMapStructured = competitorMapResult.structuredContent || {}; const competitorMapMessage = String(competitorMapStructured.assistantMessage || ''); if (competitorMapStructured.status !== 'ok' || !competitorMapMessage.includes('竞品图谱') || !competitorMapMessage.includes('错位竞争') || !competitorMapStructured.data?.competitors?.length) { throw new Error(`Expected VOC competitor map result, got: ${JSON.stringify(competitorMapStructured)}`); } const businessWorkflowResult = await client.callTool({ name: 'voc_business_workflow_run', arguments: { brand: 'MCP示例品牌', industry: '家居定制', platform: 'douyin', collectionMode: 'sample', keywords: ['家居定制怎么选', '第一次买怎么对比', '售后'] } }); const businessWorkflowStructured = businessWorkflowResult.structuredContent || {}; const businessWorkflowMessage = String(businessWorkflowStructured.assistantMessage || ''); if (businessWorkflowStructured.status !== 'ok' || !businessWorkflowMessage.includes('VOC经营闭环') || businessWorkflowStructured.summary?.workflowStage !== 'business_workflow_first_round') { throw new Error(`Expected VOC business workflow result, got: ${JSON.stringify(businessWorkflowStructured)}`); } for (const tool of ['voc_api_search', 'voc_api_doc', 'voc_api_call']) { if (!toolNames.includes(tool)) { throw new Error(`Expected catalog tool ${tool}, got: ${toolNames.join(', ')}`); } } const apiSearchResult = await client.callTool({ name: 'voc_api_search', arguments: { platform: 'douyin' } }); const apiSearchStructured = apiSearchResult.structuredContent || {}; if (apiSearchStructured.status !== 'ok' || !apiSearchStructured.data?.endpoints?.length) { throw new Error(`Expected voc_api_search to list douyin endpoints, got: ${JSON.stringify(apiSearchStructured)}`); } const taobaoDetailSearchResult = await client.callTool({ name: 'voc_api_search', arguments: { platform: 'taobao', query: '淘宝 商品详情' } }); const taobaoDetailEndpoints = taobaoDetailSearchResult.structuredContent?.data?.endpoints || []; const taobaoDetailIds = taobaoDetailEndpoints.map(endpoint => endpoint.id); if (taobaoDetailIds[0] !== 'taobao.get_item_detail_v6') { throw new Error(`Expected current Taobao endpoints with v6 preferred for product details, got: ${taobaoDetailIds.join(', ')}`); } const taobaoCatalogResult = await client.callTool({ name: 'voc_api_search', arguments: { platform: 'taobao' } }); const taobaoCatalogIds = (taobaoCatalogResult.structuredContent?.data?.endpoints || []).map(endpoint => endpoint.id); for (const id of ['taobao.get_item_detail_v3', 'taobao.get_item_detail_v6', 'taobao.get_shop_item_list_v4']) { if (!taobaoCatalogIds.includes(id)) { throw new Error(`Expected current Taobao catalog to include ${id}, got: ${taobaoCatalogIds.join(', ')}`); } } const taobaoSalesSearchResult = await client.callTool({ name: 'voc_api_search', arguments: { platform: 'taobao', query: '淘宝 页面销量' } }); const taobaoSalesEndpoints = taobaoSalesSearchResult.structuredContent?.data?.endpoints || []; if (taobaoSalesEndpoints[0]?.id !== 'taobao.get_item_detail_v3' || taobaoSalesEndpoints[0]?.deprecated) { throw new Error(`Expected Taobao v3 to be preferred for page sales, got: ${JSON.stringify(taobaoSalesEndpoints)}`); } const taobaoCouponPriceSearchResult = await client.callTool({ name: 'voc_api_search', arguments: { platform: 'taobao', query: '淘宝 券后价' } }); const taobaoCouponPriceEndpoints = taobaoCouponPriceSearchResult.structuredContent?.data?.endpoints || []; if (taobaoCouponPriceEndpoints[0]?.id !== 'taobao.get_item_detail_v4') { throw new Error(`Expected Taobao v4 to be preferred for coupon prices, got: ${JSON.stringify(taobaoCouponPriceEndpoints)}`); } const taobaoV9DocResult = await client.callTool({ name: 'voc_api_doc', arguments: { id: 'taobao.get_item_detail_v9' } }); const taobaoV9Endpoint = taobaoV9DocResult.structuredContent?.data?.endpoint || {}; if (!String(taobaoV9Endpoint.responseHint || '').includes('totalCount=') || Number(taobaoV9Endpoint.recommendationPriority) >= 0) { throw new Error(`Expected Taobao v9 field semantics and compatibility priority, got: ${JSON.stringify(taobaoV9Endpoint)}`); } const apiDocResult = await client.callTool({ name: 'voc_api_doc', arguments: { id: 'douyin.search_general' } }); const apiDocStructured = apiDocResult.structuredContent || {}; if (apiDocStructured.status !== 'ok' || !String(apiDocStructured.data?.endpoint?.proxyPath || '').includes('fetch_general_search_v2')) { throw new Error(`Expected voc_api_doc to return douyin search doc, got: ${JSON.stringify(apiDocStructured)}`); } const apiCallSentinel = 'sk-SMOKE_WRONG_TOKEN_TYPE'; const apiCallResult = await client.callTool({ name: 'voc_api_call', arguments: { id: 'douyin.search_general', params: { keyword: '律师' }, newapiToken: apiCallSentinel, vocToken: 'r:SMOKE_INVALID_SESSION_TOKEN' } }); const apiCallStructured = apiCallResult.structuredContent || {}; if (apiCallStructured.status !== 'needs_valid_token') { throw new Error(`Expected voc_api_call to map invalid sk-/r: tokens to needs_valid_token (token problem, not 余额不足), got: ${JSON.stringify(apiCallStructured)}`); } if (JSON.stringify(apiCallStructured).includes(apiCallSentinel) || JSON.stringify(apiCallStructured).includes('r:SMOKE_INVALID_SESSION_TOKEN')) { throw new Error('voc_api_call leaked the request-scoped token'); } console.log(JSON.stringify({ status: 'ok', tools: toolNames, assistantMessagePreview: assistantMessage.slice(0, 120), deepDivePreview: deepDiveMessage.slice(0, 120), contentPlanPreview: contentPlanMessage.slice(0, 120), speakingScriptPreview: speakingScriptMessage.slice(0, 120), competitorMapPreview: competitorMapMessage.slice(0, 120), businessWorkflowPreview: businessWorkflowMessage.slice(0, 120), preferenceMemoryApplied: Boolean(structured.summary && structured.summary.preferenceMemoryApplied), files: structured.files || [] }, null, 2)); } finally { await client.close(); } } main().catch(error => { console.error(error); process.exit(1); });