UNPKG

aodianyun-lcps-mcp

Version:

奥点云活动导播台工程MCP服务

1,075 lines (937 loc) 46.1 kB
#!/usr/bin/env node import {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js"; import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js"; // 解析命令行参数 function parseArgs() { const args = process.argv.slice(2); const config = { accessId: null, accessKey: null, baseUrl: null, domain: null }; for (let i = 0; i < args.length; i++) { switch (args[i]) { case '--access-id': case '-i': config.accessId = args[i + 1]; i++; break; case '--access-key': case '-k': config.accessKey = args[i + 1]; i++; break; case '--base-url': case '-u': config.baseUrl = args[i + 1]; i++; break; case '--domain': case '-d': config.domain = args[i + 1]; i++; break; case '--help': case '-h': console.log(` 奥点云活动导播台工程 MCP 服务 用法: npx aodianyun-lcps-mcp [选项] 选项: -i, --access-id <id> 奥点云访问ID -k, --access-key <key> 奥点云访问密钥 -u, --base-url <url> API基础URL -d, --domain <domain> 奥点云域名 (默认: jstest.aodianyun.cn) -h, --help 显示帮助信息 环境变量: AODIANYUN_ACCESS_ID 奥点云访问ID AODIANYUN_ACCESS_KEY 奥点云访问密钥 AODIANYUN_BASE_URL API基础URL AODIANYUN_DOMAIN 奥点云域名 示例: npx aodianyun-lcps-mcp -i "481280170004" -k "66QxB9Y6b6020C1ouRWrLUSJZixwpYLW" npx aodianyun-lcps-mcp --access-id "481280170004" --access-key "66QxB9Y6b6020C1ouRWrLUSJZixwpYLW" -d "prod.aodianyun.cn" `); process.exit(0); break; } } return config; } console.log("奥点云活动导播台工程 MCP 服务启动中..."); import fetch from "node-fetch"; import {z} from "zod"; import crypto from "crypto"; // 创建MCP服务器 const server = new McpServer({ name: "aodianyun-lcps-mcp", version: "1.0.0" }); // 获取命令行参数 const CLI_CONFIG = parseArgs(); // 获取域名配置 const DOMAIN = CLI_CONFIG.domain || process.env.AODIANYUN_DOMAIN || "jstest.aodianyun.cn"; // 鉴权配置 const AUTH_CONFIG = { accessId: CLI_CONFIG.accessId || process.env.AODIANYUN_ACCESS_ID || "481280170004", accessKey: CLI_CONFIG.accessKey || process.env.AODIANYUN_ACCESS_KEY || "66QxB9Y6b6020C1ouRWrLUSJZixwpYLW", baseUrl: CLI_CONFIG.baseUrl || process.env.AODIANYUN_BASE_URL || `http://apaas-api.${DOMAIN}` }; // 配置验证 function validateConfig() { if (!AUTH_CONFIG.accessId || !AUTH_CONFIG.accessKey) { console.error('❌ 缺少必要的配置参数'); console.error('请提供 accessId 和 accessKey 参数'); console.error('示例: npx aodianyun-lcps-mcp -i "481280170004" -k "66QxB9Y6b6020C1ouRWrLUSJZixwpYLW"'); return false; } console.log('✅ 配置验证通过了'); console.log(`🌐 域名: ${DOMAIN}`); console.log(`📡 API地址: ${AUTH_CONFIG.baseUrl}`); console.log(`🆔 访问ID: ${AUTH_CONFIG.accessId}`); return true; } // 生成签名 function generateSignature(accessId, accessKey, signatureNonce, timestamp, body) { const stringToSign = `${accessId}\n${accessKey}\n${signatureNonce}\n${timestamp}\n${body}`; return crypto.createHash('md5').update(stringToSign).digest('hex'); } // 生成请求头 function generateHeaders(body) { const timestamp = Math.floor(Date.now() / 1000); const signatureNonce = Math.random().toString(36).substring(2, 15); const signature = generateSignature( AUTH_CONFIG.accessId, AUTH_CONFIG.accessKey, signatureNonce, timestamp, body ); return { 'Content-Type': 'application/json', 'X-AccessId': AUTH_CONFIG.accessId, 'X-TimeStamp': timestamp.toString(), 'X-SignatureNonce': signatureNonce, 'X-Signature': signature }; } // 通用API请求函数 async function makeApiRequest(endpoint, body) { try { const bodyString = JSON.stringify(body); const headers = generateHeaders(bodyString); const response = await fetch(`${AUTH_CONFIG.baseUrl}${endpoint}`, { method: 'POST', headers: headers, body: bodyString }); const data = await response.json(); return data; } catch (error) { console.error(`API请求出错 (${endpoint}):`, error); throw error; } } // 获取LCPS认证Token的辅助函数 async function getLcpsAuthToken(id, secretKey, pathname) { try { const authUrl = `https://lcpsconnect.${DOMAIN}/console/lcpsApp/getPwd`; const authBody = { id: id, secretKey: secretKey, pathname: pathname }; const authResponse = await fetch(authUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(authBody) }); const authResult = await authResponse.json(); if (authResult.Flag === 100) { return { success: true, headers: { 'Content-Type': 'application/json', 'x-validate': authResult.data.validate, 'x-timestamp': authResult.data.timestamp.toString(), 'x-rand': authResult.data.rand.toString() }, data: authResult.data }; } else { return { success: false, error: authResult.FlagString }; } } catch (error) { return { success: false, error: error.message }; } } // 1. 创建活动导播台工程 server.tool( "create_active_lcps_project", { title: z.string().describe("工程标题"), inputNum: z.number().int().min(4).max(8).describe("信源数目(路数),可选值:4, 5, 6, 7, 8"), resolution: z.enum(["720p", "1080p"]).describe("分辨率,可选值:720p, 1080p"), useHours: z.number().int().min(1).default(1).describe("使用时长(小时),默认1小时"), subType: z.string().default("alcps").describe("固定值alcps"), remarks: z.string().optional().describe("备注信息(可选,默认自动生成resolution和inputNum的组合)") }, async ({title, inputNum, resolution, useHours, subType, remarks}) => { try { // 自动生成remarks,如果用户没有提供的话 const autoRemarks = remarks || `${resolution}_${inputNum}路`; const body = { title, inputNum, resolution, useHours, subType, remarks: autoRemarks }; const result = await makeApiRequest("/openMcp/activeLcps/createProject", body); let responseText = `🎬 创建活动导播台工程结果:\n\n`; responseText += `📝 工程标题: ${title}\n`; responseText += `📊 信源数目: ${inputNum}\n`; responseText += `🖥️ 分辨率: ${resolution}\n`; responseText += `⏰ 使用时长: ${useHours}小时\n`; responseText += `📝 备注信息: ${autoRemarks}\n\n`; if (result.flag === 100) { responseText += `✅ 创建成功!\n`; responseText += `🆔 工程ID: ${result.data.id}\n`; responseText += `📋 状态: ${result.flagString}\n`; } else { responseText += `❌ 创建失败!\n`; responseText += `⚠️ 错误信息: ${result.flagString}\n`; } return { content: [{type: "text", text: responseText.trim()}] }; } catch (error) { return { content: [{type: "text", text: `创建活动导播台工程出错: ${error.message}`}], isError: true }; } } ); // 2. 开启活动导播台工程 server.tool( "open_active_lcps_project", { projectId: z.number().int().describe("工程ID"), expiresTime: z.number().int().describe("到期时间(秒级时间戳)"), subType: z.string().default("alcps").describe("固定值alcps") }, async ({projectId, expiresTime, subType}) => { try { const body = { projectId, expiresTime, subType }; const result = await makeApiRequest("/openMcp/activeLcps/openProject", body); let responseText = `🚀 开启活动导播台工程结果:\n\n`; responseText += `🆔 工程ID: ${projectId}\n`; responseText += `⏰ 到期时间: ${new Date(expiresTime * 1000).toLocaleString()}\n\n`; if (result.flag === 100) { responseText += `✅ 开启成功!\n`; responseText += `📋 状态: ${result.flagString}\n`; if (result.data && result.data.lcpsAddr) { responseText += `📡 LCPS地址: ${result.data.lcpsAddr}\n`; responseText += `🔗 完整地址: http://${result.data.lcpsAddr}\n`; } } else { responseText += `❌ 开启失败!\n`; responseText += `⚠️ 错误信息: ${result.flagString}\n`; } return { content: [{type: "text", text: responseText.trim()}] }; } catch (error) { return { content: [{type: "text", text: `开启活动导播台工程出错: ${error.message}`}], isError: true }; } } ); // 3. 关闭活动导播台工程 server.tool( "close_active_lcps_project", { projectId: z.string().describe("工程ID"), subType: z.string().default("alcps").describe("固定值alcps") }, async ({projectId, subType}) => { try { const body = { projectId, subType }; const result = await makeApiRequest("/openMcp/activeLcps/closeProject", body); let responseText = `🛑 关闭活动导播台工程结果:\n\n`; responseText += `🆔 工程ID: ${projectId}\n\n`; if (result.flag === 100) { responseText += `✅ 关闭成功!\n`; responseText += `📋 状态: ${result.flagString}\n`; } else { responseText += `❌ 关闭失败!\n`; responseText += `⚠️ 错误信息: ${result.flagString}\n`; } return { content: [{type: "text", text: responseText.trim()}] }; } catch (error) { return { content: [{type: "text", text: `关闭活动导播台工程出错: ${error.message}`}], isError: true }; } } ); // 4. 删除活动导播台工程 server.tool( "delete_active_lcps_project", { projectId: z.string().describe("工程ID"), subType: z.string().default("alcps").describe("固定值alcps") }, async ({projectId, subType}) => { try { const body = { projectId, subType }; const result = await makeApiRequest("/openMcp/activeLcps/deleteProject", body); let responseText = `🗑️ 删除活动导播台工程结果:\n\n`; responseText += `🆔 工程ID: ${projectId}\n\n`; if (result.flag === 100) { responseText += `✅ 删除成功!\n`; responseText += `📋 状态: ${result.flagString}\n`; } else { responseText += `❌ 删除失败!\n`; responseText += `⚠️ 错误信息: ${result.flagString}\n`; } return { content: [{type: "text", text: responseText.trim()}] }; } catch (error) { return { content: [{type: "text", text: `删除活动导播台工程出错: ${error.message}`}], isError: true }; } } ); // 5. 获取活动导播台工程列表 server.tool( "get_active_lcps_project_list", { num: z.number().int().min(1).max(100).default(10).describe("每页数目,默认10,最大100"), page: z.number().int().min(1).default(1).describe("页数,默认1"), subType: z.string().default("alcps").describe("固定值alcps") }, async ({num, page, subType}) => { try { const body = { num, page, subType }; const result = await makeApiRequest("/openMcp/activeLcps/getProjectList", body); let responseText = `📋 获取活动导播台工程列表结果:\n\n`; responseText += `📄 页码: ${page}\n`; responseText += `📊 每页数量: ${num}\n\n`; if (result.flag === 100) { responseText += `✅ 获取成功!\n`; responseText += `📋 状态: ${result.flagString}\n`; responseText += `📈 总工程数: ${result.data.total}\n\n`; if (result.data && Array.isArray(result.data)) { responseText += `🎬 工程列表:\n`; responseText += `─`.repeat(50) + `\n`; result.data.forEach((project, index) => { const statusText = project.status === 1 ? "🟢 开启" : "🔴 关闭"; const createTime = new Date(project.createtime * 1000).toLocaleString(); responseText += `${index + 1}. ${project.title}\n`; responseText += ` 🆔 工程ID: ${project.id}\n`; responseText += ` 📊 信源数目: ${project.inputNum}\n`; responseText += ` 🖥️ 分辨率: ${project.resolution}\n`; responseText += ` 📊 状态: ${statusText}\n`; responseText += ` 📅 创建时间: ${createTime}\n`; responseText += ` ─`.repeat(30) + `\n`; }); } else { responseText += `📝 暂无工程数据\n`; } } else { responseText += `❌ 获取失败!\n`; responseText += `⚠️ 错误信息: ${result.flagString}\n`; } return { content: [{type: "text", text: responseText.trim()}] }; } catch (error) { return { content: [{type: "text", text: `获取活动导播台工程列表出错: ${error.message}`}], isError: true }; } } ); // 6. 获取活动导播台中控页面 server.tool( "get_active_lcps_control_page", { projectId: z.number().int().describe("工程ID"), subType: z.string().default("alcps").describe("固定值alcps") }, async ({projectId, subType}) => { try { const body = { projectId, subType }; const result = await makeApiRequest("/openMcp/activeLcps/enterToolProject", body); let responseText = `🎛️ 获取活动导播台中控页面结果:\n\n`; responseText += `🆔 工程ID: ${projectId}\n\n`; if (result.flag === 100) { responseText += `✅ 获取成功!\n`; responseText += `📋 状态: ${result.flagString}\n\n`; responseText += `🔗 中控页面链接:\n`; responseText += `${result.data.data}\n\n`; responseText += `💡 提示: 复制上述链接到浏览器即可访问中控页面`; // 提取认证信息 if (result.data && result.data.id && result.data.secretKey) { responseText += `\n🔐 认证信息:\n`; responseText += `🆔 ID: ${result.data.id}\n`; responseText += `🔑 SecretKey: ${result.data.secretKey}\n`; } } else { responseText += `❌ 获取失败!\n`; responseText += `⚠️ 错误信息: ${result.flagString}\n`; } return { content: [{type: "text", text: responseText.trim()}] }; } catch (error) { return { content: [{type: "text", text: `获取活动导播台中控页面出错: ${error.message}`}], isError: true }; } } ); // 6.1. 获取LCPS认证Token server.tool( "get_lcps_auth_token", { id: z.string().describe("LCPS实例ID"), secretKey: z.string().describe("LCPS密钥"), pathname: z.string().default("/getToken").describe("认证路径,默认为/getToken"), projectId: z.number().int().optional().describe("工程ID(用于enterToolProject调用)"), subType: z.string().default("alcps").describe("固定值alcps") }, async ({id, secretKey, pathname, projectId, subType}) => { try { let responseText = `🔐 获取LCPS认证Token结果:\n\n`; responseText += `🆔 实例ID: ${id}\n`; responseText += `🔑 密钥: ${secretKey}\n`; responseText += `📂 路径: ${pathname}\n\n`; // 1. 获取LCPS认证Token const authUrl = `https://lcpsconnect.${DOMAIN}/console/lcpsApp/getPwd`; const authBody = { id: id, secretKey: secretKey, pathname: pathname }; const authResponse = await fetch(authUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(authBody) }); const authResult = await authResponse.json(); if (authResult.Flag === 100) { responseText += `✅ LCPS认证成功!\n`; responseText += `📋 状态: ${authResult.FlagString}\n\n`; responseText += `🔐 认证信息:\n`; responseText += ` Validate: ${authResult.data.validate}\n`; responseText += ` Timestamp: ${authResult.data.timestamp}\n`; responseText += ` Rand: ${authResult.data.rand}\n\n`; } else { responseText += `❌ LCPS认证失败!\n`; responseText += `⚠️ 错误信息: ${authResult.FlagString}\n\n`; } // 2. 如果提供了projectId,调用enterToolProject if (projectId) { responseText += `🎛️ 调用enterToolProject...\n`; const enterToolBody = { projectId, subType }; const enterToolResult = await makeApiRequest("/openMcp/activeLcps/enterToolProject", enterToolBody); if (enterToolResult.flag === 100) { responseText += `✅ enterToolProject调用成功!\n`; responseText += `📋 状态: ${enterToolResult.flagString}\n\n`; responseText += `🔗 中控页面链接:\n`; responseText += `${enterToolResult.data.data}\n\n`; // 从data.data字符串中解析id和secretKey try { const urlData = enterToolResult.data.data; const urlParams = new URLSearchParams(urlData.split('?')[1]); const extractedId = urlParams.get('id'); const extractedSecretKey = urlParams.get('secretKey'); const subToken = urlParams.get('subToken'); const userCode = urlParams.get('userCode'); if (extractedId && extractedSecretKey) { responseText += `🔐 解析到的认证信息:\n`; responseText += ` 🆔 ID: ${extractedId}\n`; responseText += ` 🔑 SecretKey: ${extractedSecretKey}\n`; responseText += ` 🎫 SubToken: ${subToken}\n`; responseText += ` 👤 UserCode: ${userCode}\n\n`; // 更新id和secretKey为解析出的值 id = extractedId; secretKey = extractedSecretKey; responseText += `🔄 已更新认证参数,将使用解析出的ID和SecretKey重新获取认证Token\n\n`; // 使用解析出的认证信息重新获取LCPS认证Token const updatedAuthBody = { id: extractedId, secretKey: extractedSecretKey, pathname: pathname }; const updatedAuthResponse = await fetch(authUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updatedAuthBody) }); const updatedAuthResult = await updatedAuthResponse.json(); if (updatedAuthResult.Flag === 100) { responseText += `✅ 使用解析的认证信息重新获取Token成功!\n`; responseText += `🔐 更新后的认证信息:\n`; responseText += ` Validate: ${updatedAuthResult.data.validate}\n`; responseText += ` Timestamp: ${updatedAuthResult.data.timestamp}\n`; responseText += ` Rand: ${updatedAuthResult.data.rand}\n\n`; } else { responseText += `❌ 使用解析的认证信息重新获取Token失败: ${updatedAuthResult.FlagString}\n\n`; } } else { responseText += `⚠️ 无法从URL中解析出ID和SecretKey\n\n`; } } catch (error) { responseText += `❌ 解析URL参数时出错: ${error.message}\n\n`; } } else { responseText += `❌ enterToolProject调用失败!\n`; responseText += `⚠️ 错误信息: ${enterToolResult.flagString}\n\n`; } } responseText += `💡 提示: 使用认证信息作为请求头 x-validate, x-timestamp, x-rand`; return { content: [{type: "text", text: responseText.trim()}] }; } catch (error) { return { content: [{type: "text", text: `获取LCPS认证Token出错: ${error.message}`}], isError: true }; } } ); // 7. 设置信道参数 server.tool( "set_channel", { lcpsAddr: z.string().describe("LCPS地址(从open_active_lcps_project返回的lcpsAddr,支持带http协议)"), channel: z.number().int().min(1).max(16).describe("信道号(1-16,对应API的0-15)"), keepRatio: z.boolean().optional().describe("是否保持视频比例"), playURL: z.array(z.string()).optional().describe("拉流地址数组"), inputType: z.string().default("拉流").describe("输入类型,默认为拉流"), allowPublishForce: z.boolean().default(false).describe("是否允许强制发布,默认false"), firstBufferTime: z.number().int().default(0).describe("首次缓冲时间(秒),默认0"), maxBufferTime: z.number().int().default(1).describe("最大缓冲时间(秒),默认1"), smoothMode: z.boolean().default(false).describe("平滑模式,默认false"), enableLowLatency: z.boolean().default(false).describe("启用低延迟,默认false"), id: z.string().optional().describe("LCPS实例ID(从get_active_lcps_control_page获取)"), secretKey: z.string().optional().describe("LCPS密钥(从get_active_lcps_control_page获取)"), projectId: z.number().int().optional().describe("工程ID(用于自动获取认证信息)") }, async ({lcpsAddr, channel, keepRatio, playURL, inputType, allowPublishForce, firstBufferTime, maxBufferTime, smoothMode, enableLowLatency, id, secretKey, projectId}) => { try { // 将用户输入的1-16转换为API需要的0-15 const apiChannel = channel - 1; let responseText = `🎛️ 批量设置信道参数结果:\n\n`; responseText += `📡 LCPS地址: ${lcpsAddr}\n`; responseText += `📺 用户信道号: ${channel} (对应API信道: ${apiChannel})\n\n`; const results = []; let authHeaders = { 'Content-Type': 'application/json' }; // 自动获取认证信息 let finalId = id; let finalSecretKey = secretKey; // 如果提供了projectId,自动调用get_lcps_auth_token获取认证信息 if (projectId && (!id || !secretKey)) { responseText += `🔐 正在自动获取认证信息...\n`; try { // 调用enterToolProject获取认证信息 const enterToolBody = { projectId, subType: "alcps" }; const enterToolResult = await makeApiRequest("/openMcp/activeLcps/enterToolProject", enterToolBody); if (enterToolResult.flag === 100) { responseText += `✅ 成功获取工程信息\n`; // 从URL中解析认证信息 try { const urlData = enterToolResult.data.data; const urlParams = new URLSearchParams(urlData.split('?')[1]); const extractedId = urlParams.get('id'); const extractedSecretKey = urlParams.get('secretKey'); if (extractedId && extractedSecretKey) { finalId = extractedId; finalSecretKey = extractedSecretKey; responseText += `🔐 解析到认证信息:\n`; responseText += ` 🆔 ID: ${extractedId}\n`; responseText += ` 🔑 SecretKey: ${extractedSecretKey}\n\n`; } else { responseText += `❌ 无法从工程信息中解析认证参数\n\n`; } } catch (error) { responseText += `❌ 解析认证信息时出错: ${error.message}\n\n`; } } else { responseText += `❌ 获取工程信息失败: ${enterToolResult.flagString}\n\n`; } } catch (error) { responseText += `❌ 自动获取认证信息时出错: ${error.message}\n\n`; } } // 如果有了认证信息,显示状态 if (finalId && finalSecretKey) { responseText += `🔐 认证信息已准备,将在各API调用时获取对应Token\n\n`; } else { responseText += `⚠️ 未提供认证信息,将使用无认证模式\n\n`; } // 1. 设置视频保持比例(如果提供) if (keepRatio !== undefined) { try { // 确保lcpsAddr包含http协议 const baseUrl = lcpsAddr.startsWith('http') ? lcpsAddr : `http://${lcpsAddr}`; const keepRatioUrl = `${baseUrl}/setVideoKeepRatio`; const keepRatioBody = { channel: apiChannel, keepRatio }; let headers = { 'Content-Type': 'application/json' }; // 如果提供了认证信息,获取对应的认证token if (finalId && finalSecretKey) { const authResult = await getLcpsAuthToken(finalId, finalSecretKey, '/setVideoKeepRatio'); if (authResult.success) { headers = authResult.headers; responseText += `🔐 /setVideoKeepRatio 认证成功\n`; } else { responseText += `❌ /setVideoKeepRatio 认证失败: ${authResult.error}\n`; } } const keepRatioResponse = await fetch(keepRatioUrl, { method: 'POST', headers: headers, body: JSON.stringify(keepRatioBody) }); const keepRatioResult = await keepRatioResponse.json(); results.push({ operation: '设置视频保持比例', success: keepRatioResponse.ok, result: keepRatioResult }); } catch (error) { results.push({ operation: '设置视频保持比例', success: false, error: error.message }); } } // 2. 切换输入源(如果提供) if (playURL && playURL.length > 0) { try { // 确保lcpsAddr包含http协议 const baseUrl = lcpsAddr.startsWith('http') ? lcpsAddr : `http://${lcpsAddr}`; const changeInputUrl = `${baseUrl}/changeInput`; const changeInputBody = { channel: apiChannel, playURL, inputType, allowPublishForce, firstBufferTime, maxBufferTime }; let headers = { 'Content-Type': 'application/json' }; // 如果提供了认证信息,获取对应的认证token if (finalId && finalSecretKey) { const authResult = await getLcpsAuthToken(finalId, finalSecretKey, '/changeInput'); if (authResult.success) { headers = authResult.headers; responseText += `🔐 /changeInput 认证成功\n`; } else { responseText += `❌ /changeInput 认证失败: ${authResult.error}\n`; } } const changeInputResponse = await fetch(changeInputUrl, { method: 'POST', headers: headers, body: JSON.stringify(changeInputBody) }); const changeInputResult = await changeInputResponse.json(); results.push({ operation: '切换输入源', success: changeInputResponse.ok, result: changeInputResult }); } catch (error) { results.push({ operation: '切换输入源', success: false, error: error.message }); } } // 3. 设置缓冲时间 try { // 确保lcpsAddr包含http协议 const baseUrl = lcpsAddr.startsWith('http') ? lcpsAddr : `http://${lcpsAddr}`; const bufferTimeUrl = `${baseUrl}/setBufferTime`; const bufferTimeBody = { channel: apiChannel, smoothMode, enableLowLatency }; let headers = { 'Content-Type': 'application/json' }; // 如果提供了认证信息,获取对应的认证token if (finalId && finalSecretKey) { const authResult = await getLcpsAuthToken(finalId, finalSecretKey, '/setBufferTime'); if (authResult.success) { headers = authResult.headers; responseText += `🔐 /setBufferTime 认证成功\n`; } else { responseText += `❌ /setBufferTime 认证失败: ${authResult.error}\n`; } } const bufferTimeResponse = await fetch(bufferTimeUrl, { method: 'POST', headers: headers, body: JSON.stringify(bufferTimeBody) }); const bufferTimeResult = await bufferTimeResponse.json(); results.push({ operation: '设置缓冲时间', success: bufferTimeResponse.ok, result: bufferTimeResult }); } catch (error) { results.push({ operation: '设置缓冲时间', success: false, error: error.message }); } // 输出结果 results.forEach((result, index) => { const statusIcon = result.success ? '✅' : '❌'; responseText += `${index + 1}. ${statusIcon} ${result.operation}\n`; if (result.success) { responseText += ` 📋 响应: ${JSON.stringify(result.result)}\n`; } else { responseText += ` ⚠️ 错误: ${result.error}\n`; } responseText += `\n`; }); const successCount = results.filter(r => r.success).length; const totalCount = results.length; responseText += `📊 执行统计:\n`; responseText += `✅ 成功: ${successCount}/${totalCount}\n`; responseText += `❌ 失败: ${totalCount - successCount}/${totalCount}\n`; if (successCount === totalCount) { responseText += `🎉 所有操作执行成功!`; } else if (successCount > 0) { responseText += `⚠️ 部分操作执行成功`; } else { responseText += `❌ 所有操作执行失败`; } return { content: [{type: "text", text: responseText.trim()}] }; } catch (error) { return { content: [{type: "text", text: `批量设置信道参数出错: ${error.message}`}], isError: true }; } } ); // 8. 设置导播台输出地址 server.tool( "set_output_address", { lcpsAddr: z.string().describe("LCPS地址(从open_active_lcps_project返回的lcpsAddr,支持带http协议)"), publishURL: z.array(z.string()).describe("推流地址数组"), formatIndex: z.number().int().default(0).describe("格式索引,默认0"), notRestart: z.boolean().default(false).describe("是否不重启,默认false"), enable: z.boolean().default(true).describe("是否启用,默认true"), playURL: z.string().default("").describe("播放地址,默认为空"), videoSize: z.string().default("1920x1080").describe("视频尺寸,默认1920x1080"), videoBitrate: z.string().default("3000k").describe("视频码率,默认3000k"), videoCBR: z.boolean().default(false).describe("视频CBR模式,默认false"), qmin: z.number().int().default(2).describe("最小量化参数,默认2"), qmax: z.number().int().default(1024).describe("最大量化参数,默认1024"), gop: z.number().int().default(3).describe("GOP大小,默认3"), audioBitrate: z.string().default("128k").describe("音频码率,默认128k"), audioOnly: z.boolean().default(false).describe("仅音频模式,默认false"), enableDelayPublish: z.boolean().default(false).describe("启用延迟发布,默认false"), delayPublishTime: z.number().int().default(120).describe("延迟发布时间(秒),默认120"), delayPublishURL: z.array(z.string()).default([]).describe("延迟发布地址数组,默认为空"), publishToLPS: z.boolean().default(false).describe("发布到LPS,默认false"), videoRecord: z.boolean().default(false).describe("视频录制,默认false"), lossless: z.boolean().default(false).describe("无损模式,默认false"), hdr: z.boolean().default(false).describe("HDR模式,默认false"), pixfmt: z.string().default("yuv420").describe("像素格式,默认yuv420"), hdrtype: z.string().default("HLG").describe("HDR类型,默认HLG"), id: z.string().optional().describe("LCPS实例ID(从get_active_lcps_control_page获取)"), secretKey: z.string().optional().describe("LCPS密钥(从get_active_lcps_control_page获取)"), projectId: z.number().int().optional().describe("工程ID(用于自动获取认证信息)") }, async ({lcpsAddr, publishURL, formatIndex, notRestart, enable, playURL, videoSize, videoBitrate, videoCBR, qmin, qmax, gop, audioBitrate, audioOnly, enableDelayPublish, delayPublishTime, delayPublishURL, publishToLPS, videoRecord, lossless, hdr, pixfmt, hdrtype, id, secretKey, projectId}) => { try { let responseText = `📡 设置输出地址结果:\n\n`; responseText += `📡 LCPS地址: ${lcpsAddr}\n`; responseText += `🔗 推流地址: ${publishURL.join(', ')}\n`; responseText += `🎬 视频尺寸: ${videoSize}\n`; responseText += `📊 视频码率: ${videoBitrate}\n`; responseText += `🎵 音频码率: ${audioBitrate}\n\n`; // 自动获取认证信息 let finalId = id; let finalSecretKey = secretKey; // 如果提供了projectId,自动调用get_lcps_auth_token获取认证信息 if (projectId && (!id || !secretKey)) { responseText += `🔐 正在自动获取认证信息...\n`; try { // 调用enterToolProject获取认证信息 const enterToolBody = { projectId, subType: "alcps" }; const enterToolResult = await makeApiRequest("/openMcp/activeLcps/enterToolProject", enterToolBody); if (enterToolResult.flag === 100) { responseText += `✅ 成功获取工程信息\n`; // 从URL中解析认证信息 try { const urlData = enterToolResult.data.data; const urlParams = new URLSearchParams(urlData.split('?')[1]); const extractedId = urlParams.get('id'); const extractedSecretKey = urlParams.get('secretKey'); if (extractedId && extractedSecretKey) { finalId = extractedId; finalSecretKey = extractedSecretKey; responseText += `🔐 解析到认证信息:\n`; responseText += ` 🆔 ID: ${extractedId}\n`; responseText += ` 🔑 SecretKey: ${extractedSecretKey}\n\n`; } else { responseText += `❌ 无法从工程信息中解析认证参数\n\n`; } } catch (error) { responseText += `❌ 解析认证信息时出错: ${error.message}\n\n`; } } else { responseText += `❌ 获取工程信息失败: ${enterToolResult.flagString}\n\n`; } } catch (error) { responseText += `❌ 自动获取认证信息时出错: ${error.message}\n\n`; } } // 如果有了认证信息,显示状态 if (finalId && finalSecretKey) { responseText += `🔐 认证信息已准备,将在API调用时获取对应Token\n\n`; } else { responseText += `⚠️ 未提供认证信息,将使用无认证模式\n\n`; } // 调用/changeOutput接口设置输出地址 try { // 确保lcpsAddr包含http协议 const baseUrl = lcpsAddr.startsWith('http') ? lcpsAddr : `http://${lcpsAddr}`; const changeOutputUrl = `${baseUrl}/changeOutput`; const changeOutputBody = { formatIndex, notRestart, enable, publishURL, playURL, videoSize, videoBitrate, videoCBR, qmin, qmax, gop, audioBitrate, audioOnly, enableDelayPublish, delayPublishTime, delayPublishURL, publishToLPS, videoRecord, lossless, hdr, pixfmt, hdrtype }; let headers = { 'Content-Type': 'application/json' }; // 如果提供了认证信息,获取对应的认证token if (finalId && finalSecretKey) { const authResult = await getLcpsAuthToken(finalId, finalSecretKey, '/changeOutput'); if (authResult.success) { headers = authResult.headers; responseText += `🔐 /changeOutput 认证成功\n`; } else { responseText += `❌ /changeOutput 认证失败: ${authResult.error}\n`; } } const changeOutputResponse = await fetch(changeOutputUrl, { method: 'POST', headers: headers, body: JSON.stringify(changeOutputBody) }); const changeOutputResult = await changeOutputResponse.json(); if (changeOutputResponse.ok) { responseText += `✅ 设置输出地址成功!\n`; responseText += `📋 响应: ${JSON.stringify(changeOutputResult)}\n`; } else { responseText += `❌ 设置输出地址失败!\n`; responseText += `⚠️ 错误信息: ${JSON.stringify(changeOutputResult)}\n`; } } catch (error) { responseText += `❌ 调用/changeOutput接口时出错: ${error.message}\n`; } return { content: [{type: "text", text: responseText.trim()}] }; } catch (error) { return { content: [{type: "text", text: `设置输出地址出错: ${error.message}`}], isError: true }; } } ); // 启动服务器 async function main() { try { console.log("启动活动导播台MCP服务器..."); // 验证配置 if (!validateConfig()) { process.exit(1); } const transport = new StdioServerTransport(); await server.connect(transport); console.log("活动导播台MCP服务器已经开始等待连接"); } catch (error) { console.error("启动服务器时出错:", error); process.exit(1); } } main();