#!/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); 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 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 teaContentPlanResult = await client.callTool({ name: 'voc_content_plan_run', arguments: { brand: '茶饮新客转化', industry: '茶饮', issues: [ '第一次来怎么点最稳', '18-22 元贵在哪/值在哪', '券后价和套餐怎么买最划算', '茶底/甜度/配料怎么选', '哪几款不容易踩雷', '外卖到手怎么避免翻车', '买一送一/团购规则怎么讲清楚' ], evidenceText: '小红书评论:波霸奶茶用的什么茶底?我要避雷!!下午喝的,晚上睁眼到天亮。抖音线索:9.9 新品、11.9 单人下午茶、21 元买一送一能触发注意,但还需要解释规则、口味和是否有坑。' } }); const teaContentPlanStructured = teaContentPlanResult.structuredContent || {}; const teaContentPlanMessage = String(teaContentPlanStructured.assistantMessage || ''); if (teaContentPlanStructured.status !== 'ok' || !teaContentPlanMessage.includes('茶底怎么选') || !teaContentPlanMessage.includes('买一送一到底怎么算') || teaContentPlanMessage.includes('招牌菜') || teaContentPlanMessage.includes('下饭菜')) { throw new Error(`Expected tea-specific VOC content plan result, got: ${JSON.stringify(teaContentPlanStructured)}`); } 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)}`); } 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); });